tapioca 0.4.17 → 0.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9293078ee1614e88f45586904e3d90cc556cab3218340699325902e224d94aed
4
- data.tar.gz: d14050953a3de23bfc4830b6b27282331dcac745090695cd2552a5e7a1ee6296
3
+ metadata.gz: b3709ab7f54589bdf97715ffc2cc7c3a98ad703a6e318a264db70429078cdf26
4
+ data.tar.gz: 3e38366a54f9b3286de28a2b6d557762b2cf564848b8184a76aadc75504a41fb
5
5
  SHA512:
6
- metadata.gz: bd3bbcbc3a7d5448e7ace8472258e96c654d8854f1c4ec3ffe498a5b06e136f46df526f5a83d4b7814bf1ed57eec24cb3694c8fce686dd2df02a6d6af6de8414
7
- data.tar.gz: 641e472a327784295d117481c9676b7fc92c320a764d06449ed9c5d65e1f5cc76cb63b0446e4269d85fd606df8ec19e519ce42cfe503403a1cf2197c042ad494
6
+ metadata.gz: 4ef5e31a85a71607340ad54e2fff59b1a5bd0d3f81b1481bd4830ca5e73e5ea9ed1504d38808d8bf49a9686276685167a35103769fdb30d66b61362c4d9c072b
7
+ data.tar.gz: e0b1daab32f024cc5f3f1fc019b5c1dcd040ce0657e59b81d736702e3cb380a9e7265397f108f4787163e50d9b31e793b960cd33f875cf45a061cee17fa1857e
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ > :warning: **Note**: This software is currently under active development. The API and interface should be considered unstable until a v1.0.0 release.
2
+
1
3
  # Tapioca
2
4
 
3
5
  ![Build Status](https://github.com/Shopify/tapioca/workflows/CI/badge.svg)
@@ -14,7 +16,7 @@ For gems that have a normal default `require` and load all of their constants th
14
16
 
15
17
  For example, suppose you are using the class `BetterHtml::Parser` exported from the `better_html` gem. Just doing a `require "better_html"` (which is the default require) does not load that type:
16
18
 
17
- ```ruby
19
+ ```shell
18
20
  $ bundle exec pry
19
21
  [1] pry(main)> require 'better_html'
20
22
  => true
@@ -110,7 +112,7 @@ This will generate DSL RBIs for specified constants (or for all handled constant
110
112
  - `--prerequire [file]`: A file to be required before `Bundler.require` is called.
111
113
  - `--postrequire [file]`: A file to be required after `Bundler.require` is called.
112
114
  - `--out [directory]`: The output directory for generated RBI files, default to `sorbet/rbi/gems`.
113
- - `--generate-command [command]`: The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
115
+ - `--generate-command [command]`: **[DEPRECATED]** The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
114
116
  - `--typed-overrides [gem:level]`: Overrides typed sigils for generated gem RBIs for gem `gem` to level `level` (`level` can be one of `ignore`, `false`, `true`, `strict`, or `strong`, see [the Sorbet docs](https://sorbet.org/docs/static#file-level-granularity-strictness-levels) for more details).
115
117
 
116
118
  ## Contributing
data/exe/tapioca CHANGED
@@ -1,6 +1,21 @@
1
1
  #! /usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "../lib/tapioca"
4
+ require 'sorbet-runtime'
5
5
 
6
- Tapioca::Cli.start(ARGV)
6
+ begin
7
+ T::Configuration.default_checked_level = :never
8
+ # Suppresses errors caused by T.cast, T.let, T.must, etc.
9
+ T::Configuration.inline_type_error_handler = ->(*) {}
10
+ # Suppresses errors caused by incorrect parameter ordering
11
+ T::Configuration.sig_validation_error_handler = ->(*) {}
12
+ rescue
13
+ # Need this rescue so that if another gem has
14
+ # already set the checked level by the time we
15
+ # get to it, we don't fail outright.
16
+ nil
17
+ end
18
+
19
+ require_relative "../lib/tapioca/internal"
20
+
21
+ Tapioca::Cli::Main.start(ARGV)
data/lib/tapioca.rb CHANGED
@@ -17,31 +17,5 @@ module Tapioca
17
17
  class Error < StandardError; end
18
18
  end
19
19
 
20
- begin
21
- T::Configuration.default_checked_level = :never
22
- # Suppresses errors caused by T.cast, T.let, T.must, etc.
23
- T::Configuration.inline_type_error_handler = ->(*) {}
24
- # Suppresses errors caused by incorrect parameter ordering
25
- T::Configuration.sig_validation_error_handler = ->(*) {}
26
- rescue
27
- # Need this rescue so that if another gem has
28
- # already set the checked level by the time we
29
- # get to it, we don't fail outright.
30
- nil
31
- end
32
-
33
- require "tapioca/loader"
34
- require "tapioca/constant_locator"
35
- require "tapioca/config"
36
- require "tapioca/config_builder"
37
- require "tapioca/generator"
38
- require "tapioca/cli"
39
- require "tapioca/gemfile"
40
- require "tapioca/compilers/sorbet"
41
- require "tapioca/compilers/requires_compiler"
42
- require "tapioca/compilers/symbol_table_compiler"
43
- require "tapioca/compilers/symbol_table/symbol_generator"
44
- require "tapioca/compilers/symbol_table/symbol_loader"
45
- require "tapioca/compilers/todos_compiler"
46
- require "tapioca/compilers/dsl_compiler"
20
+ require "tapioca/compilers/dsl/base"
47
21
  require "tapioca/version"
data/lib/tapioca/cli.rb CHANGED
@@ -4,112 +4,5 @@
4
4
  require 'thor'
5
5
 
6
6
  module Tapioca
7
- class Cli < Thor
8
- include(Thor::Actions)
9
-
10
- class_option :prerequire,
11
- aliases: ["--pre", "-b"],
12
- banner: "file",
13
- desc: "A file to be required before Bundler.require is called"
14
- class_option :postrequire,
15
- aliases: ["--post", "-a"],
16
- banner: "file",
17
- desc: "A file to be required after Bundler.require is called"
18
- class_option :outdir,
19
- aliases: ["--out", "-o"],
20
- banner: "directory",
21
- desc: "The output directory for generated RBI files"
22
- class_option :generate_command,
23
- aliases: ["--cmd", "-c"],
24
- banner: "command",
25
- desc: "The command to run to regenerate RBI files"
26
- class_option :exclude,
27
- aliases: ["-x"],
28
- type: :array,
29
- banner: "gem [gem ...]",
30
- desc: "Excludes the given gem(s) from RBI generation"
31
- class_option :typed_overrides,
32
- aliases: ["--typed", "-t"],
33
- type: :hash,
34
- banner: "gem:level [gem:level ...]",
35
- desc: "Overrides for typed sigils for generated gem RBIs"
36
-
37
- map T.unsafe(%w[--version -v] => :__print_version)
38
-
39
- desc "init", "initializes folder structure"
40
- def init
41
- create_file(Config::SORBET_CONFIG, skip: true) do
42
- <<~CONTENT
43
- --dir
44
- .
45
- CONTENT
46
- end
47
- create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
48
- <<~CONTENT
49
- # typed: false
50
- # frozen_string_literal: true
51
-
52
- # Add your extra requires here
53
- CONTENT
54
- end
55
- end
56
-
57
- desc "require", "generate the list of files to be required by tapioca"
58
- def require
59
- Tapioca.silence_warnings do
60
- generator.build_requires
61
- end
62
- end
63
-
64
- desc "todo", "generate the list of unresolved constants"
65
- def todo
66
- Tapioca.silence_warnings do
67
- generator.build_todos
68
- end
69
- end
70
-
71
- desc "dsl [constant...]", "generate RBIs for dynamic methods"
72
- option :generators,
73
- type: :array,
74
- aliases: ["--gen", "-g"],
75
- banner: "generator [generator ...]",
76
- desc: "Only run supplied DSL generators"
77
- def dsl(*constants)
78
- Tapioca.silence_warnings do
79
- generator.build_dsl(constants)
80
- end
81
- end
82
-
83
- desc "generate [gem...]", "generate RBIs from gems"
84
- def generate(*gems)
85
- Tapioca.silence_warnings do
86
- generator.build_gem_rbis(gems)
87
- end
88
- end
89
-
90
- desc "sync", "sync RBIs to Gemfile"
91
- def sync
92
- Tapioca.silence_warnings do
93
- generator.sync_rbis_with_gemfile
94
- end
95
- end
96
-
97
- desc "--version, -v", "show version"
98
- def __print_version
99
- puts "Tapioca v#{Tapioca::VERSION}"
100
- end
101
-
102
- no_commands do
103
- def self.exit_on_failure?
104
- true
105
- end
106
-
107
- def generator
108
- current_command = T.must(current_command_chain.first)
109
- @generator ||= Generator.new(
110
- ConfigBuilder.from_options(current_command, options)
111
- )
112
- end
113
- end
114
- end
7
+ module Cli; end
115
8
  end
@@ -0,0 +1,136 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Cli
6
+ class Main < Thor
7
+ include(Thor::Actions)
8
+
9
+ class_option :prerequire,
10
+ aliases: ["--pre", "-b"],
11
+ banner: "file",
12
+ desc: "A file to be required before Bundler.require is called"
13
+ class_option :postrequire,
14
+ aliases: ["--post", "-a"],
15
+ banner: "file",
16
+ desc: "A file to be required after Bundler.require is called"
17
+ class_option :outdir,
18
+ aliases: ["--out", "-o"],
19
+ banner: "directory",
20
+ desc: "The output directory for generated RBI files"
21
+ class_option :generate_command,
22
+ aliases: ["--cmd", "-c"],
23
+ banner: "command",
24
+ desc: "The command to run to regenerate RBI files"
25
+ class_option :exclude,
26
+ aliases: ["-x"],
27
+ type: :array,
28
+ banner: "gem [gem ...]",
29
+ desc: "Excludes the given gem(s) from RBI generation"
30
+ class_option :typed_overrides,
31
+ aliases: ["--typed", "-t"],
32
+ type: :hash,
33
+ banner: "gem:level [gem:level ...]",
34
+ desc: "Overrides for typed sigils for generated gem RBIs"
35
+
36
+ map T.unsafe(%w[--version -v] => :__print_version)
37
+
38
+ desc "init", "initializes folder structure"
39
+ def init
40
+ create_config
41
+ create_post_require
42
+ generate_binstub
43
+ end
44
+
45
+ desc "require", "generate the list of files to be required by tapioca"
46
+ def require
47
+ Tapioca.silence_warnings do
48
+ generator.build_requires
49
+ end
50
+ end
51
+
52
+ desc "todo", "generate the list of unresolved constants"
53
+ def todo
54
+ Tapioca.silence_warnings do
55
+ generator.build_todos
56
+ end
57
+ end
58
+
59
+ desc "dsl [constant...]", "generate RBIs for dynamic methods"
60
+ option :generators,
61
+ type: :array,
62
+ aliases: ["--gen", "-g"],
63
+ banner: "generator [generator ...]",
64
+ desc: "Only run supplied DSL generators"
65
+ option :verify,
66
+ type: :boolean,
67
+ default: false,
68
+ desc: "Verifies RBIs are up-to-date"
69
+ def dsl(*constants)
70
+ Tapioca.silence_warnings do
71
+ generator.build_dsl(constants, should_verify: options[:verify])
72
+ end
73
+ end
74
+
75
+ desc "generate [gem...]", "generate RBIs from gems"
76
+ def generate(*gems)
77
+ Tapioca.silence_warnings do
78
+ generator.build_gem_rbis(gems)
79
+ end
80
+ end
81
+
82
+ desc "sync", "sync RBIs to Gemfile"
83
+ def sync
84
+ Tapioca.silence_warnings do
85
+ generator.sync_rbis_with_gemfile
86
+ end
87
+ end
88
+
89
+ desc "--version, -v", "show version"
90
+ def __print_version
91
+ puts "Tapioca v#{Tapioca::VERSION}"
92
+ end
93
+
94
+ private
95
+
96
+ def create_config
97
+ create_file(Config::SORBET_CONFIG, skip: true) do
98
+ <<~CONTENT
99
+ --dir
100
+ .
101
+ CONTENT
102
+ end
103
+ end
104
+
105
+ def create_post_require
106
+ create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
107
+ <<~CONTENT
108
+ # typed: false
109
+ # frozen_string_literal: true
110
+
111
+ # Add your extra requires here
112
+ CONTENT
113
+ end
114
+ end
115
+
116
+ def generate_binstub
117
+ installer = Bundler::Installer.new(Bundler.root, Bundler.definition)
118
+ spec = Bundler.definition.specs.find { |s| s.name == "tapioca" }
119
+ installer.generate_bundler_executable_stubs(spec, { force: true })
120
+ end
121
+
122
+ no_commands do
123
+ def self.exit_on_failure?
124
+ true
125
+ end
126
+
127
+ def generator
128
+ current_command = T.must(current_command_chain.first)
129
+ @generator ||= Generator.new(
130
+ ConfigBuilder.from_options(current_command, options)
131
+ )
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -371,7 +371,6 @@ module Tapioca
371
371
  return unless signature
372
372
 
373
373
  return_type = signature.return_type
374
- return if T::Types::Simple === return_type && T::Generic === return_type.raw_type
375
374
  return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
376
375
 
377
376
  return_type.to_s
@@ -382,10 +381,11 @@ module Tapioca
382
381
  signature = T::Private::Methods.signature_for_method(column_type.method(method))
383
382
  return unless signature
384
383
 
385
- arg_type = signature.arg_types.first.last
386
- return if T::Types::Simple === arg_type && T::Generic === arg_type.raw_type
384
+ # Arg types is an array [name, type] entries, so we desctructure the type of
385
+ # first argument to get the first argument type
386
+ _, first_argument_type = signature.arg_types.first
387
387
 
388
- arg_type.to_s
388
+ first_argument_type.to_s
389
389
  end
390
390
 
391
391
  sig { params(type: String).returns(String) }
@@ -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)
@@ -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
@@ -142,9 +148,11 @@ module Tapioca
142
148
  def compile_object(name, value)
143
149
  return if symbol_ignored?(name)
144
150
  klass = class_of(value)
145
- 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::")
146
154
 
147
- type_name = public_module?(klass) && name_of(klass) || "T.untyped"
155
+ type_name = public_module?(klass) && klass_name || "T.untyped"
148
156
  indented("#{name} = T.let(T.unsafe(nil), #{type_name})")
149
157
  end
150
158
 
@@ -181,16 +189,17 @@ module Tapioca
181
189
 
182
190
  [
183
191
  compile_module_helpers(constant),
192
+ compile_type_variables(constant),
184
193
  compile_mixins(constant),
185
194
  compile_mixes_in_class_methods(constant),
186
195
  compile_props(constant),
187
196
  compile_enums(constant),
188
197
  methods,
189
- ].select { |b| b != "" }.join("\n\n")
198
+ ].select { |b| b && !b.empty? }.join("\n\n")
190
199
  end
191
200
  end
192
201
 
193
- sig { params(constant: Module).returns(String) }
202
+ sig { params(constant: Module).returns(T.nilable(String)) }
194
203
  def compile_module_helpers(constant)
195
204
  abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
196
205
 
@@ -199,12 +208,14 @@ module Tapioca
199
208
  helpers << indented("final!") if T::Private::Final.final_module?(constant)
200
209
  helpers << indented("sealed!") if T::Private::Sealed.sealed_module?(constant)
201
210
 
211
+ return if helpers.empty?
212
+
202
213
  helpers.join("\n")
203
214
  end
204
215
 
205
- sig { params(constant: Module).returns(String) }
216
+ sig { params(constant: Module).returns(T.nilable(String)) }
206
217
  def compile_props(constant)
207
- return "" unless T::Props::ClassMethods === constant
218
+ return unless T::Props::ClassMethods === constant
208
219
 
209
220
  constant.props.map do |name, prop|
210
221
  method = "prop"
@@ -219,11 +230,11 @@ module Tapioca
219
230
  end.join("\n")
220
231
  end
221
232
 
222
- sig { params(constant: Module).returns(String) }
233
+ sig { params(constant: Module).returns(T.nilable(String)) }
223
234
  def compile_enums(constant)
224
- return "" unless T::Enum > constant
235
+ return unless T::Enum > constant
225
236
 
226
- enums = T.cast(constant, T::Enum).values.map do |enum_type|
237
+ enums = T.unsafe(constant).values.map do |enum_type|
227
238
  enum_type.instance_variable_get(:@const_name).to_s
228
239
  end
229
240
 
@@ -250,11 +261,71 @@ module Tapioca
250
261
  compile(symbol, subconstant)
251
262
  end.compact
252
263
 
253
- return "" if output.empty?
264
+ return if output.empty?
254
265
 
255
266
  "\n" + output.join("\n\n")
256
267
  end
257
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
+
258
329
  sig { params(constant: Class).returns(String) }
259
330
  def compile_superclass(constant)
260
331
  superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
@@ -431,29 +502,19 @@ module Tapioca
431
502
  instance_methods = compile_directly_owned_methods(name, constant)
432
503
  singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
433
504
 
434
- return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty?
505
+ return if symbol_ignored?(name) && !instance_methods && !singleton_methods
435
506
 
436
507
  [
437
- initialize_method || "",
508
+ initialize_method,
438
509
  instance_methods,
439
510
  singleton_methods,
440
- ].select { |b| b.strip != "" }.join("\n\n")
511
+ ].select { |b| b && !b.strip.empty? }.join("\n\n")
441
512
  end
442
513
 
443
- 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)) }
444
515
  def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
445
- indent_step = 0
446
- preamble = nil
447
- postamble = nil
448
-
449
- if mod.singleton_class?
450
- indent_step = 1
451
- preamble = indented("class << self")
452
- postamble = indented("end")
453
- end
454
-
455
- methods = with_indentation(indent_step) do
456
- method_names_by_visibility(mod)
516
+ with_indentation_for_constant(mod) do
517
+ methods = method_names_by_visibility(mod)
457
518
  .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
458
519
  .flat_map do |visibility, method_list|
459
520
  compiled = method_list.sort!.map do |name|
@@ -470,16 +531,11 @@ module Tapioca
470
531
  compiled
471
532
  end
472
533
  .compact
473
- .join("\n")
474
- end
475
534
 
476
- return "" if methods.strip == ""
535
+ return if methods.empty?
477
536
 
478
- [
479
- preamble,
480
- methods,
481
- postamble,
482
- ].compact.join("\n")
537
+ methods.join("\n")
538
+ end
483
539
  end
484
540
 
485
541
  sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
@@ -659,6 +715,33 @@ module Tapioca
659
715
  @indent -= 2 * step
660
716
  end
661
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
+
662
745
  sig { params(str: String).returns(String) }
663
746
  def indented(str)
664
747
  " " * @indent + str
@@ -859,12 +942,12 @@ module Tapioca
859
942
  nil
860
943
  end
861
944
 
862
- sig { params(constant: Module).returns(String) }
945
+ sig { params(constant: T::Types::Base).returns(String) }
863
946
  def type_of(constant)
864
947
  constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
865
948
  end
866
949
 
867
- sig { params(object: Object).returns(T::Boolean).checked(:never) }
950
+ sig { params(object: BasicObject).returns(Integer).checked(:never) }
868
951
  def object_id_of(object)
869
952
  Object.instance_method(:object_id).bind(object).call
870
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,
@@ -63,7 +63,7 @@ module Tapioca
63
63
 
64
64
  content = String.new
65
65
  content << rbi_header(
66
- config.generate_command,
66
+ "#{Config::DEFAULT_COMMAND} require",
67
67
  reason: "explicit gem requires",
68
68
  strictness: "false"
69
69
  )
@@ -76,8 +76,8 @@ module Tapioca
76
76
  say("Done", :green)
77
77
 
78
78
  say("All requires from this application have been written to #{name}.", [:green, :bold])
79
- cmd = set_color("tapioca sync", :yellow, :bold)
80
- say("Please review changes and commit them, then run #{cmd}.", [:green, :bold])
79
+ cmd = set_color("#{Config::DEFAULT_COMMAND} sync", :yellow, :bold)
80
+ say("Please review changes and commit them, then run `#{cmd}`.", [:green, :bold])
81
81
  end
82
82
 
83
83
  sig { void }
@@ -99,7 +99,7 @@ module Tapioca
99
99
 
100
100
  content = String.new
101
101
  content << rbi_header(
102
- config.generate_command,
102
+ "#{Config::DEFAULT_COMMAND} todo",
103
103
  reason: "unresolved constants",
104
104
  strictness: "false"
105
105
  )
@@ -116,16 +116,26 @@ module Tapioca
116
116
  say("Please review changes and commit them.", [:green, :bold])
117
117
  end
118
118
 
119
- sig { params(requested_constants: T::Array[String]).void }
120
- def build_dsl(requested_constants)
119
+ sig do
120
+ params(
121
+ requested_constants: T::Array[String],
122
+ should_verify: T::Boolean,
123
+ ).void
124
+ end
125
+ def build_dsl(requested_constants, should_verify: false)
121
126
  load_application(eager_load: requested_constants.empty?)
122
127
  load_dsl_generators
123
128
 
124
- rbi_files_to_purge = existing_rbi_filenames(requested_constants)
125
-
126
- say("Compiling DSL RBI files...")
129
+ if should_verify
130
+ say("Checking for out-of-date RBIs...")
131
+ else
132
+ say("Compiling DSL RBI files...")
133
+ end
127
134
  say("")
128
135
 
136
+ outpath = should_verify ? Dir.mktmpdir : config.outpath
137
+ rbi_files_to_purge = existing_rbi_filenames(requested_constants)
138
+
129
139
  compiler = Compilers::DslCompiler.new(
130
140
  requested_constants: constantize(requested_constants),
131
141
  requested_generators: config.generators,
@@ -135,24 +145,21 @@ module Tapioca
135
145
  )
136
146
 
137
147
  compiler.run do |constant, contents|
138
- filename = compile_dsl_rbi(constant, contents)
148
+ filename = compile_dsl_rbi(constant, contents, outpath: Pathname.new(outpath))
139
149
  rbi_files_to_purge.delete(filename) if filename
140
150
  end
151
+ say("")
141
152
 
142
- unless rbi_files_to_purge.empty?
143
- say("")
144
- say("Removing stale RBI files...")
145
-
146
- rbi_files_to_purge.sort.each do |filename|
147
- remove(filename)
148
- end
149
- end
153
+ if should_verify
154
+ perform_dsl_verification(outpath)
155
+ else
156
+ purge_stale_dsl_rbi_files(rbi_files_to_purge)
150
157
 
151
- say("")
152
- say("Done", :green)
158
+ say("Done", :green)
153
159
 
154
- say("All operations performed in working directory.", [:green, :bold])
155
- say("Please review changes and commit them.", [:green, :bold])
160
+ say("All operations performed in working directory.", [:green, :bold])
161
+ say("Please review changes and commit them.", [:green, :bold])
162
+ end
156
163
  end
157
164
 
158
165
  sig { void }
@@ -218,7 +225,7 @@ module Tapioca
218
225
  say_error("If you populated ", :yellow)
219
226
  say_error("#{file} ", :bold, :blue)
220
227
  say_error("with ", :yellow)
221
- say_error("tapioca require", :bold, :blue)
228
+ say_error("`#{Config::DEFAULT_COMMAND} require`", :bold, :blue)
222
229
  say_error("you should probably review it and remove the faulty line.", :yellow)
223
230
  end
224
231
 
@@ -286,10 +293,10 @@ module Tapioca
286
293
  constant_map.values
287
294
  end
288
295
 
289
- sig { params(requested_constants: T::Array[String]).returns(T::Set[Pathname]) }
290
- def existing_rbi_filenames(requested_constants)
296
+ sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
297
+ def existing_rbi_filenames(requested_constants, path: config.outpath)
291
298
  filenames = if requested_constants.empty?
292
- Pathname.glob(config.outpath / "**/*.rbi")
299
+ Pathname.glob(path / "**/*.rbi")
293
300
  else
294
301
  requested_constants.map do |constant_name|
295
302
  dsl_rbi_filename(constant_name)
@@ -477,7 +484,7 @@ module Tapioca
477
484
  rbi_body_content = compiler.compile(gem)
478
485
  content = String.new
479
486
  content << rbi_header(
480
- config.generate_command,
487
+ "#{Config::DEFAULT_COMMAND} sync",
481
488
  reason: "types exported from the `#{gem.name}` gem",
482
489
  strictness: strictness
483
490
  )
@@ -494,23 +501,22 @@ module Tapioca
494
501
  end
495
502
  File.write(filename.to_s, content)
496
503
 
497
- Pathname.glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
504
+ T.unsafe(Pathname).glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
498
505
  remove(file) unless file.basename.to_s == gem.rbi_file_name
499
506
  end
500
507
  end
501
508
 
502
- sig { params(constant: Module, contents: String).returns(T.nilable(Pathname)) }
503
- def compile_dsl_rbi(constant, contents)
509
+ sig { params(constant: Module, contents: String, outpath: Pathname).returns(T.nilable(Pathname)) }
510
+ def compile_dsl_rbi(constant, contents, outpath: config.outpath)
504
511
  return if contents.nil?
505
512
 
506
- command = format(config.generate_command, constant.name)
507
513
  constant_name = Module.instance_method(:name).bind(constant).call
508
514
  rbi_name = constant_name.underscore + ".rbi"
509
- filename = config.outpath / rbi_name
515
+ filename = outpath / rbi_name
510
516
 
511
517
  out = String.new
512
518
  out << rbi_header(
513
- command,
519
+ "#{Config::DEFAULT_COMMAND} dsl #{constant_name}",
514
520
  reason: "dynamic methods in `#{constant.name}`"
515
521
  )
516
522
  out << contents
@@ -522,5 +528,53 @@ module Tapioca
522
528
 
523
529
  filename
524
530
  end
531
+
532
+ sig { params(tmp_dir: Pathname).returns(T.nilable(String)) }
533
+ def verify_dsl_rbi(tmp_dir:)
534
+ existing_rbis = existing_rbi_filenames([]).sort
535
+ new_rbis = existing_rbi_filenames([], path: tmp_dir).grep_v(/gem|shim/).sort
536
+
537
+ return "New file(s) introduced." if existing_rbis.length != new_rbis.length
538
+
539
+ desynced_files = []
540
+
541
+ (0..existing_rbis.length - 1).each do |i|
542
+ desynced_files << new_rbis[i] unless FileUtils.identical?(existing_rbis[i], new_rbis[i])
543
+ end
544
+
545
+ unless desynced_files.empty?
546
+ filenames = desynced_files.map { |f| f.to_s.sub!(tmp_dir.to_s, "sorbet/rbi/dsl") }.join("\n - ")
547
+
548
+ return "File(s) updated:\n - #{filenames}"
549
+ end
550
+
551
+ nil
552
+ end
553
+
554
+ sig { params(dir: String).void }
555
+ def perform_dsl_verification(dir)
556
+ if (error = verify_dsl_rbi(tmp_dir: Pathname.new(dir)))
557
+ say("RBI files are out-of-date, please run `#{Config::DEFAULT_COMMAND} dsl` to update.")
558
+ say("Reason: ", [:red])
559
+ say(error)
560
+ exit(1)
561
+ else
562
+ say("Nothing to do, all RBIs are up-to-date.")
563
+ end
564
+ ensure
565
+ FileUtils.remove_entry(dir)
566
+ end
567
+
568
+ sig { params(files: T::Set[Pathname]).void }
569
+ def purge_stale_dsl_rbi_files(files)
570
+ if files.any?
571
+ say("Removing stale RBI files...")
572
+
573
+ files.sort.each do |filename|
574
+ remove(filename)
575
+ end
576
+ say("")
577
+ end
578
+ end
525
579
  end
526
580
  end
@@ -0,0 +1,170 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ # This class is responsible for storing and looking up information related to generic types.
6
+ #
7
+ # The class stores 2 different kinds of data, in two separate lookup tables:
8
+ # 1. a lookup of generic type instances by name: `@generic_instances`
9
+ # 2. a lookup of type variable serializer by constant and type variable
10
+ # instance: `@type_variables`
11
+ #
12
+ # By storing the above data, we can cheaply query each constant against this registry
13
+ # to see if it declares any generic type variables. This becomes a simple lookup in the
14
+ # `@type_variables` hash table with the given constant.
15
+ #
16
+ # If there is no entry, then we can cheaply know that we can skip generic type
17
+ # information generation for this type.
18
+ #
19
+ # On the other hand, if we get a result, then the result will be a hash of type
20
+ # variable to type variable serializers. This allows us to associate type variables
21
+ # to the constant names that represent them, easily.
22
+ module GenericTypeRegistry
23
+ @generic_instances = T.let(
24
+ {},
25
+ T::Hash[String, Module]
26
+ )
27
+
28
+ @type_variables = T.let(
29
+ {},
30
+ T::Hash[Integer, T::Hash[Integer, String]]
31
+ )
32
+
33
+ class << self
34
+ extend T::Sig
35
+
36
+ # This method is responsible for building the name of the instantiated concrete type
37
+ # and cloning the given constant so that we can return a type that is the same
38
+ # as the current type but is a different instance and has a different name method.
39
+ #
40
+ # We cache those cloned instances by their name in `@generic_instances`, so that
41
+ # we don't keep instantiating a new type every single time it is referenced.
42
+ # For example, `[Foo[Integer], Foo[Integer], Foo[Integer], Foo[String]]` will only
43
+ # result in 2 clones (1 for `Foo[Integer]` and another for `Foo[String]`) and
44
+ # 2 hash lookups (for the other two `Foo[Integer]`s).
45
+ #
46
+ # This method returns the created or cached clone of the constant.
47
+ sig { params(constant: T.untyped, types: T.untyped).returns(Module) }
48
+ def register_type(constant, types)
49
+ # Build the name of the instantiated generic type,
50
+ # something like `"Foo[X, Y, Z]"`
51
+ type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
52
+ name = "#{name_of(constant)}[#{type_list}]"
53
+
54
+ # Create a clone of the constant with an overridden `name`
55
+ # method that returns the name we constructed above.
56
+ #
57
+ # Also, we try to memoize the clone based on the name, so that
58
+ # we don't have to keep recreating clones all the time.
59
+ @generic_instances[name] ||= constant.clone.tap do |clone|
60
+ clone.define_singleton_method(:name) { name }
61
+ end
62
+ end
63
+
64
+ sig do
65
+ params(
66
+ constant: T.untyped,
67
+ type_member: T::Types::TypeVariable,
68
+ fixed: T.untyped,
69
+ lower: T.untyped,
70
+ upper: T.untyped
71
+ ).void
72
+ end
73
+ def register_type_member(constant, type_member, fixed, lower, upper)
74
+ register_type_variable(constant, :type_member, type_member, fixed, lower, upper)
75
+ end
76
+
77
+ sig do
78
+ params(
79
+ constant: T.untyped,
80
+ type_template: T::Types::TypeVariable,
81
+ fixed: T.untyped,
82
+ lower: T.untyped,
83
+ upper: T.untyped
84
+ ).void
85
+ end
86
+ def register_type_template(constant, type_template, fixed, lower, upper)
87
+ register_type_variable(constant, :type_template, type_template, fixed, lower, upper)
88
+ end
89
+
90
+ sig { params(constant: Module).returns(T.nilable(T::Hash[Integer, String])) }
91
+ def lookup_type_variables(constant)
92
+ @type_variables[object_id_of(constant)]
93
+ end
94
+
95
+ private
96
+
97
+ # This method is called from intercepted calls to `type_member` and `type_template`.
98
+ # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
99
+ # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
100
+ #
101
+ # This method creates a `String` with that data and stores it in the
102
+ # `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
103
+ #
104
+ # Finally, the original `type_variable` is returned from this method, so that the caller
105
+ # can return it from the original methods as well.
106
+ sig do
107
+ params(
108
+ constant: T.untyped,
109
+ type_variable_type: T.enum([:type_member, :type_template]),
110
+ type_variable: T::Types::TypeVariable,
111
+ fixed: T.untyped,
112
+ lower: T.untyped,
113
+ upper: T.untyped
114
+ ).void
115
+ end
116
+ # rubocop:disable Metrics/ParameterLists
117
+ def register_type_variable(constant, type_variable_type, type_variable, fixed, lower, upper)
118
+ # rubocop:enable Metrics/ParameterLists
119
+ type_variables = lookup_or_initialize_type_variables(constant)
120
+
121
+ type_variables[object_id_of(type_variable)] = serialize_type_variable(
122
+ type_variable_type,
123
+ type_variable.variance,
124
+ fixed,
125
+ lower,
126
+ upper
127
+ )
128
+ end
129
+
130
+ sig { params(constant: Module).returns(T::Hash[Integer, String]) }
131
+ def lookup_or_initialize_type_variables(constant)
132
+ @type_variables[object_id_of(constant)] ||= {}
133
+ end
134
+
135
+ sig do
136
+ params(
137
+ type_variable_type: Symbol,
138
+ variance: Symbol,
139
+ fixed: T.untyped,
140
+ lower: T.untyped,
141
+ upper: T.untyped
142
+ ).returns(String)
143
+ end
144
+ def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
145
+ parts = []
146
+ parts << ":#{variance}" unless variance == :invariant
147
+ parts << "fixed: #{fixed}" if fixed
148
+ parts << "lower: #{lower}" unless lower == T.untyped
149
+ parts << "upper: #{upper}" unless upper == BasicObject
150
+
151
+ parameters = parts.join(", ")
152
+
153
+ serialized = T.let(type_variable_type.to_s, String)
154
+ serialized += "(#{parameters})" unless parameters.empty?
155
+
156
+ serialized
157
+ end
158
+
159
+ sig { params(constant: Module).returns(T.nilable(String)) }
160
+ def name_of(constant)
161
+ Module.instance_method(:name).bind(constant).call
162
+ end
163
+
164
+ sig { params(object: BasicObject).returns(Integer) }
165
+ def object_id_of(object)
166
+ Object.instance_method(:object_id).bind(object).call
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca"
5
+ require "tapioca/loader"
6
+ require "tapioca/constant_locator"
7
+ require "tapioca/generic_type_registry"
8
+ require "tapioca/sorbet_ext/generic_name_patch"
9
+ require "tapioca/config"
10
+ require "tapioca/config_builder"
11
+ require "tapioca/generator"
12
+ require "tapioca/cli"
13
+ require "tapioca/cli/main"
14
+ require "tapioca/gemfile"
15
+ require "tapioca/compilers/sorbet"
16
+ require "tapioca/compilers/requires_compiler"
17
+ require "tapioca/compilers/symbol_table_compiler"
18
+ require "tapioca/compilers/symbol_table/symbol_generator"
19
+ require "tapioca/compilers/symbol_table/symbol_loader"
20
+ require "tapioca/compilers/todos_compiler"
21
+ require "tapioca/compilers/dsl_compiler"
@@ -0,0 +1,66 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "tapioca/sorbet_ext/name_patch"
5
+
6
+ module T
7
+ module Generic
8
+ # This module intercepts calls to generic type instantiations and type variable definitions.
9
+ # Tapioca stores the data from those calls in a `GenericTypeRegistry` which can then be used
10
+ # to look up the original call details when we are trying to do code generation.
11
+ #
12
+ # We are interested in the data of the `[]`, `type_member` and `type_template` calls which
13
+ # are all needed to generate good generic information at runtime.
14
+ module TypeStoragePatch
15
+ def [](*types)
16
+ # `T::Generic#[]` just returns `self`, so let's call and store it.
17
+ constant = super
18
+ # `register_type` method builds and returns an instantiated clone of the generic type
19
+ # so, we just return that from this method as well.
20
+ Tapioca::GenericTypeRegistry.register_type(constant, types)
21
+ end
22
+
23
+ def type_member(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
24
+ # `T::Generic#type_member` just instantiates a `T::Type::TypeMember` instance and returns it.
25
+ # We use that when registering the type member and then later return it from this method.
26
+ type_member = super
27
+ Tapioca::GenericTypeRegistry.register_type_member(self, type_member, fixed, lower, upper)
28
+ type_member
29
+ end
30
+
31
+ def type_template(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
32
+ # `T::Generic#type_template` just instantiates a `T::Type::TypeTemplate` instance and returns it.
33
+ # We use that when registering the type template and then later return it from this method.
34
+ type_template = super
35
+ Tapioca::GenericTypeRegistry.register_type_template(self, type_template, fixed, lower, upper)
36
+ type_template
37
+ end
38
+ end
39
+
40
+ prepend TypeStoragePatch
41
+ end
42
+
43
+ module Types
44
+ class Simple
45
+ # This module intercepts calls to the `name` method for
46
+ # simple types, so that it can ask the name to the type if
47
+ # the type is generic, since, by this point, we've created
48
+ # a clone of that type with the `name` method returning the
49
+ # appropriate name for that specific concrete type.
50
+ module GenericNamePatch
51
+ def name
52
+ if T::Generic === @raw_type
53
+ # for types that are generic, use the name
54
+ # returned by the "name" method of this instance
55
+ @name ||= T.unsafe(@raw_type).name.freeze
56
+ else
57
+ # otherwise, fallback to the normal name lookup
58
+ super
59
+ end
60
+ end
61
+ end
62
+
63
+ prepend GenericNamePatch
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,16 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module T
5
+ module Types
6
+ class Simple
7
+ module NamePatch
8
+ def name
9
+ @name ||= Module.instance_method(:name).bind(@raw_type).call.freeze
10
+ end
11
+ end
12
+
13
+ prepend NamePatch
14
+ end
15
+ end
16
+ end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.4.17"
5
+ VERSION = "0.4.18"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.17
4
+ version: 0.4.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2021-03-11 00:00:00.000000000 Z
14
+ date: 2021-03-22 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -125,6 +125,7 @@ files:
125
125
  - exe/tapioca
126
126
  - lib/tapioca.rb
127
127
  - lib/tapioca/cli.rb
128
+ - lib/tapioca/cli/main.rb
128
129
  - lib/tapioca/compilers/dsl/action_controller_helpers.rb
129
130
  - lib/tapioca/compilers/dsl/action_mailer.rb
130
131
  - lib/tapioca/compilers/dsl/active_record_associations.rb
@@ -155,7 +156,11 @@ files:
155
156
  - lib/tapioca/core_ext/class.rb
156
157
  - lib/tapioca/gemfile.rb
157
158
  - lib/tapioca/generator.rb
159
+ - lib/tapioca/generic_type_registry.rb
160
+ - lib/tapioca/internal.rb
158
161
  - lib/tapioca/loader.rb
162
+ - lib/tapioca/sorbet_ext/generic_name_patch.rb
163
+ - lib/tapioca/sorbet_ext/name_patch.rb
159
164
  - lib/tapioca/version.rb
160
165
  homepage: https://github.com/Shopify/tapioca
161
166
  licenses: