tapioca 0.4.14 → 0.4.19

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: 1fb77a2510082edca1c068015be8fa3d25ed500762d05ccfb4a330588a76b182
4
- data.tar.gz: a75827dff3d7c5a49ea5d213cd7ce6b749fd4d6a4dc017bb4a8f734613a97031
3
+ metadata.gz: 5f64e1f934bd0528e93322b0ba116383fc7c053d1a0c35a3378664ecc3e08e69
4
+ data.tar.gz: 609a1125626ba712e042e2fb3346df852508595bf22c0187e8ded67b3c66ecf2
5
5
  SHA512:
6
- metadata.gz: 4d9283382edeed31a09989a789ee2441351e241558b97da8ffdb7d9a4aaa911ec8642bd5849d9b8e5800ea47ca4f9ae46b9e20cd3b0716670f25ca1304edea0b
7
- data.tar.gz: ec2baf1e85947f754324fd06b67cf43979b8b27ccf27f21edb929f5b5758d38c8f6ceb00eec817a7219e273abb8014ae47e2d38cdbb059335e96ddb4414543a2
6
+ metadata.gz: 0ffc0244e7bd3d86ead0c42eb2cf9e940c84d4d838143b96247e908ded022477829c92c1838f2b933294b590eae49c2cd667c57bad6219be9b983ba3c556ddff
7
+ data.tar.gz: 33f534d1521e209052a7edff35fe95979497c80e3fcf78b136c3bb13ea3471f8e1158db83cafff5f3aaf79f0cfa6a00129fb20fb2ce92a018883f94fe61e0264
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,142 @@
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
+ bin_stub_exists = File.exist?("bin/tapioca")
118
+ installer = Bundler::Installer.new(Bundler.root, Bundler.definition)
119
+ spec = Bundler.definition.specs.find { |s| s.name == "tapioca" }
120
+ installer.generate_bundler_executable_stubs(spec, { force: true })
121
+ if bin_stub_exists
122
+ shell.say_status(:force, "bin/tapioca", :yellow)
123
+ else
124
+ shell.say_status(:create, "bin/tapioca", :green)
125
+ end
126
+ end
127
+
128
+ no_commands do
129
+ def self.exit_on_failure?
130
+ true
131
+ end
132
+
133
+ def generator
134
+ current_command = T.must(current_command_chain.first)
135
+ @generator ||= Generator.new(
136
+ ConfigBuilder.from_options(current_command, options)
137
+ )
138
+ end
139
+ end
140
+ end
141
+ end
142
+ 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)
@@ -8,6 +8,7 @@ module Tapioca
8
8
  module Compilers
9
9
  module Sorbet
10
10
  SORBET = Pathname.new(Gem::Specification.find_by_name("sorbet-static").full_gem_path) / "libexec" / "sorbet"
11
+ EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
11
12
 
12
13
  class << self
13
14
  extend(T::Sig)
@@ -26,7 +27,9 @@ module Tapioca
26
27
 
27
28
  sig { returns(String) }
28
29
  def sorbet_path
29
- SORBET.to_s.shellescape
30
+ sorbet_path = ENV.fetch(EXE_PATH_ENV_VAR, SORBET)
31
+ sorbet_path = SORBET if sorbet_path.empty?
32
+ sorbet_path.to_s.shellescape
30
33
  end
31
34
  end
32
35
  end
@@ -74,9 +74,15 @@ module Tapioca
74
74
  compile(symbol, constant)
75
75
  end
76
76
 
77
- sig { params(symbol: String, inherit: T::Boolean).returns(BasicObject).checked(:never) }
78
- def resolve_constant(symbol, inherit: false)
79
- Object.const_get(symbol, inherit)
77
+ sig do
78
+ params(
79
+ symbol: String,
80
+ inherit: T::Boolean,
81
+ namespace: Module
82
+ ).returns(BasicObject).checked(:never)
83
+ end
84
+ def resolve_constant(symbol, inherit: false, namespace: Object)
85
+ namespace.const_get(symbol, inherit)
80
86
  rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
81
87
  nil
82
88
  end
@@ -95,6 +101,7 @@ module Tapioca
95
101
  return if alias_namespaced?(name)
96
102
  return if seen?(name)
97
103
  return unless parent_declares_constant?(name)
104
+ return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
98
105
 
99
106
  mark_seen(name)
100
107
  compile_constant(name, constant)
@@ -141,9 +148,11 @@ module Tapioca
141
148
  def compile_object(name, value)
142
149
  return if symbol_ignored?(name)
143
150
  klass = class_of(value)
144
- return if name_of(klass)&.start_with?("T::Types::", "T::Private::")
151
+ klass_name = name_of(klass)
152
+
153
+ return if klass_name&.start_with?("T::Types::", "T::Private::")
145
154
 
146
- type_name = public_module?(klass) && name_of(klass) || "T.untyped"
155
+ type_name = public_module?(klass) && klass_name || "T.untyped"
147
156
  indented("#{name} = T.let(T.unsafe(nil), #{type_name})")
148
157
  end
149
158
 
@@ -180,15 +189,17 @@ module Tapioca
180
189
 
181
190
  [
182
191
  compile_module_helpers(constant),
192
+ compile_type_variables(constant),
183
193
  compile_mixins(constant),
184
194
  compile_mixes_in_class_methods(constant),
185
195
  compile_props(constant),
196
+ compile_enums(constant),
186
197
  methods,
187
- ].select { |b| b != "" }.join("\n\n")
198
+ ].select { |b| b && !b.empty? }.join("\n\n")
188
199
  end
189
200
  end
190
201
 
191
- sig { params(constant: Module).returns(String) }
202
+ sig { params(constant: Module).returns(T.nilable(String)) }
192
203
  def compile_module_helpers(constant)
193
204
  abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
194
205
 
@@ -197,12 +208,14 @@ module Tapioca
197
208
  helpers << indented("final!") if T::Private::Final.final_module?(constant)
198
209
  helpers << indented("sealed!") if T::Private::Sealed.sealed_module?(constant)
199
210
 
211
+ return if helpers.empty?
212
+
200
213
  helpers.join("\n")
201
214
  end
202
215
 
203
- sig { params(constant: Module).returns(String) }
216
+ sig { params(constant: Module).returns(T.nilable(String)) }
204
217
  def compile_props(constant)
205
- return "" unless T::Props::ClassMethods === constant
218
+ return unless T::Props::ClassMethods === constant
206
219
 
207
220
  constant.props.map do |name, prop|
208
221
  method = "prop"
@@ -217,6 +230,23 @@ module Tapioca
217
230
  end.join("\n")
218
231
  end
219
232
 
233
+ sig { params(constant: Module).returns(T.nilable(String)) }
234
+ def compile_enums(constant)
235
+ return unless T::Enum > constant
236
+
237
+ enums = T.unsafe(constant).values.map do |enum_type|
238
+ enum_type.instance_variable_get(:@const_name).to_s
239
+ end
240
+
241
+ content = [
242
+ indented('enums do'),
243
+ *enums.map { |e| indented(" #{e} = new") }.join("\n"),
244
+ indented('end'),
245
+ ]
246
+
247
+ content.join("\n")
248
+ end
249
+
220
250
  sig { params(name: String, constant: Module).returns(T.nilable(String)) }
221
251
  def compile_subconstants(name, constant)
222
252
  output = constants_of(constant).sort.uniq.map do |constant_name|
@@ -231,11 +261,71 @@ module Tapioca
231
261
  compile(symbol, subconstant)
232
262
  end.compact
233
263
 
234
- return "" if output.empty?
264
+ return if output.empty?
235
265
 
236
266
  "\n" + output.join("\n\n")
237
267
  end
238
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
+
239
329
  sig { params(constant: Class).returns(String) }
240
330
  def compile_superclass(constant)
241
331
  superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
@@ -345,9 +435,13 @@ module Tapioca
345
435
  end
346
436
 
347
437
  define_singleton_method(:include) do |mod|
348
- before = singleton_class.ancestors
349
- super(mod).tap do
350
- mixins_from_modules[mod] = singleton_class.ancestors - before
438
+ begin
439
+ before = singleton_class.ancestors
440
+ super(mod).tap do
441
+ mixins_from_modules[mod] = singleton_class.ancestors - before
442
+ end
443
+ rescue Exception # rubocop:disable Lint/RescueException
444
+ # this is a best effort, bail if we can't perform this
351
445
  end
352
446
  end
353
447
 
@@ -408,29 +502,19 @@ module Tapioca
408
502
  instance_methods = compile_directly_owned_methods(name, constant)
409
503
  singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
410
504
 
411
- return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty?
505
+ return if symbol_ignored?(name) && !instance_methods && !singleton_methods
412
506
 
413
507
  [
414
- initialize_method || "",
508
+ initialize_method,
415
509
  instance_methods,
416
510
  singleton_methods,
417
- ].select { |b| b.strip != "" }.join("\n\n")
511
+ ].select { |b| b && !b.strip.empty? }.join("\n\n")
418
512
  end
419
513
 
420
- 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)) }
421
515
  def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
422
- indent_step = 0
423
- preamble = nil
424
- postamble = nil
425
-
426
- if mod.singleton_class?
427
- indent_step = 1
428
- preamble = indented("class << self")
429
- postamble = indented("end")
430
- end
431
-
432
- methods = with_indentation(indent_step) do
433
- method_names_by_visibility(mod)
516
+ with_indentation_for_constant(mod) do
517
+ methods = method_names_by_visibility(mod)
434
518
  .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
435
519
  .flat_map do |visibility, method_list|
436
520
  compiled = method_list.sort!.map do |name|
@@ -447,16 +531,11 @@ module Tapioca
447
531
  compiled
448
532
  end
449
533
  .compact
450
- .join("\n")
451
- end
452
534
 
453
- return "" if methods.strip == ""
535
+ return if methods.empty?
454
536
 
455
- [
456
- preamble,
457
- methods,
458
- postamble,
459
- ].compact.join("\n")
537
+ methods.join("\n")
538
+ end
460
539
  end
461
540
 
462
541
  sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
@@ -636,6 +715,33 @@ module Tapioca
636
715
  @indent -= 2 * step
637
716
  end
638
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
+
639
745
  sig { params(str: String).returns(String) }
640
746
  def indented(str)
641
747
  " " * @indent + str
@@ -836,12 +942,12 @@ module Tapioca
836
942
  nil
837
943
  end
838
944
 
839
- sig { params(constant: Module).returns(String) }
945
+ sig { params(constant: T::Types::Base).returns(String) }
840
946
  def type_of(constant)
841
947
  constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
842
948
  end
843
949
 
844
- sig { params(object: Object).returns(T::Boolean).checked(:never) }
950
+ sig { params(object: BasicObject).returns(Integer).checked(:never) }
845
951
  def object_id_of(object)
846
952
  Object.instance_method(:object_id).bind(object).call
847
953
  end