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