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