tapioca 0.4.15 → 0.4.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/exe/tapioca +17 -2
- data/lib/tapioca.rb +1 -27
- data/lib/tapioca/cli.rb +1 -108
- data/lib/tapioca/cli/main.rb +146 -0
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +4 -4
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +1 -1
- data/lib/tapioca/compilers/dsl/base.rb +1 -1
- data/lib/tapioca/compilers/dsl/url_helpers.rb +3 -3
- data/lib/tapioca/compilers/sorbet.rb +4 -1
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +145 -39
- data/lib/tapioca/config.rb +1 -1
- data/lib/tapioca/config_builder.rb +7 -12
- data/lib/tapioca/generator.rb +183 -28
- data/lib/tapioca/generic_type_registry.rb +219 -0
- data/lib/tapioca/internal.rb +21 -0
- data/lib/tapioca/loader.rb +13 -2
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +66 -0
- data/lib/tapioca/sorbet_ext/name_patch.rb +16 -0
- data/lib/tapioca/version.rb +1 -1
- metadata +7 -2
data/lib/tapioca/config.rb
CHANGED
@@ -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
|
-
|
14
|
-
|
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,
|
data/lib/tapioca/generator.rb
CHANGED
@@ -63,7 +63,7 @@ module Tapioca
|
|
63
63
|
|
64
64
|
content = String.new
|
65
65
|
content << rbi_header(
|
66
|
-
|
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("
|
80
|
-
say("Please review changes and commit them, then run
|
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
|
-
|
102
|
+
"#{Config::DEFAULT_COMMAND} todo",
|
103
103
|
reason: "unresolved constants",
|
104
104
|
strictness: "false"
|
105
105
|
)
|
@@ -116,14 +116,27 @@ module Tapioca
|
|
116
116
|
say("Please review changes and commit them.", [:green, :bold])
|
117
117
|
end
|
118
118
|
|
119
|
-
sig
|
120
|
-
|
119
|
+
sig do
|
120
|
+
params(
|
121
|
+
requested_constants: T::Array[String],
|
122
|
+
should_verify: T::Boolean,
|
123
|
+
quiet: T::Boolean
|
124
|
+
).void
|
125
|
+
end
|
126
|
+
def build_dsl(requested_constants, should_verify: false, quiet: false)
|
121
127
|
load_application(eager_load: requested_constants.empty?)
|
122
128
|
load_dsl_generators
|
123
129
|
|
124
|
-
|
130
|
+
if should_verify
|
131
|
+
say("Checking for out-of-date RBIs...")
|
132
|
+
else
|
133
|
+
say("Compiling DSL RBI files...")
|
134
|
+
end
|
125
135
|
say("")
|
126
136
|
|
137
|
+
outpath = should_verify ? Pathname.new(Dir.mktmpdir) : config.outpath
|
138
|
+
rbi_files_to_purge = existing_rbi_filenames(requested_constants)
|
139
|
+
|
127
140
|
compiler = Compilers::DslCompiler.new(
|
128
141
|
requested_constants: constantize(requested_constants),
|
129
142
|
requested_generators: config.generators,
|
@@ -132,15 +145,35 @@ module Tapioca
|
|
132
145
|
}
|
133
146
|
)
|
134
147
|
|
148
|
+
constant_lookup = {}
|
149
|
+
|
135
150
|
compiler.run do |constant, contents|
|
136
|
-
|
151
|
+
constant_name = Module.instance_method(:name).bind(constant).call
|
152
|
+
|
153
|
+
filename = compile_dsl_rbi(
|
154
|
+
constant_name,
|
155
|
+
contents,
|
156
|
+
outpath: outpath,
|
157
|
+
quiet: should_verify || quiet
|
158
|
+
)
|
159
|
+
|
160
|
+
if filename
|
161
|
+
rbi_files_to_purge.delete(filename)
|
162
|
+
constant_lookup[filename.relative_path_from(outpath)] = constant_name
|
163
|
+
end
|
137
164
|
end
|
138
|
-
|
139
165
|
say("")
|
140
|
-
say("Done", :green)
|
141
166
|
|
142
|
-
|
143
|
-
|
167
|
+
if should_verify
|
168
|
+
perform_dsl_verification(outpath, constant_lookup)
|
169
|
+
else
|
170
|
+
purge_stale_dsl_rbi_files(rbi_files_to_purge)
|
171
|
+
|
172
|
+
say("Done", :green)
|
173
|
+
|
174
|
+
say("All operations performed in working directory.", [:green, :bold])
|
175
|
+
say("Please review changes and commit them.", [:green, :bold])
|
176
|
+
end
|
144
177
|
end
|
145
178
|
|
146
179
|
sig { void }
|
@@ -206,7 +239,7 @@ module Tapioca
|
|
206
239
|
say_error("If you populated ", :yellow)
|
207
240
|
say_error("#{file} ", :bold, :blue)
|
208
241
|
say_error("with ", :yellow)
|
209
|
-
say_error("
|
242
|
+
say_error("`#{Config::DEFAULT_COMMAND} require`", :bold, :blue)
|
210
243
|
say_error("you should probably review it and remove the faulty line.", :yellow)
|
211
244
|
end
|
212
245
|
|
@@ -253,13 +286,38 @@ module Tapioca
|
|
253
286
|
|
254
287
|
sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
|
255
288
|
def constantize(constant_names)
|
256
|
-
constant_names.map do |name|
|
289
|
+
constant_map = constant_names.map do |name|
|
257
290
|
begin
|
258
|
-
name.constantize
|
291
|
+
[name, name.constantize]
|
259
292
|
rescue NameError
|
260
|
-
nil
|
293
|
+
[name, nil]
|
261
294
|
end
|
262
|
-
end.
|
295
|
+
end.to_h
|
296
|
+
|
297
|
+
unprocessable_constants = constant_map.select { |_, v| v.nil? }
|
298
|
+
unless unprocessable_constants.empty?
|
299
|
+
unprocessable_constants.each do |name, _|
|
300
|
+
say("Error: Cannot find constant '#{name}'", :red)
|
301
|
+
remove(dsl_rbi_filename(name))
|
302
|
+
end
|
303
|
+
|
304
|
+
exit(1)
|
305
|
+
end
|
306
|
+
|
307
|
+
constant_map.values
|
308
|
+
end
|
309
|
+
|
310
|
+
sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
|
311
|
+
def existing_rbi_filenames(requested_constants, path: config.outpath)
|
312
|
+
filenames = if requested_constants.empty?
|
313
|
+
Pathname.glob(path / "**/*.rbi")
|
314
|
+
else
|
315
|
+
requested_constants.map do |constant_name|
|
316
|
+
dsl_rbi_filename(constant_name)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
filenames.to_set
|
263
321
|
end
|
264
322
|
|
265
323
|
sig { returns(T::Hash[String, String]) }
|
@@ -277,6 +335,11 @@ module Tapioca
|
|
277
335
|
.to_h
|
278
336
|
end
|
279
337
|
|
338
|
+
sig { params(constant_name: String).returns(Pathname) }
|
339
|
+
def dsl_rbi_filename(constant_name)
|
340
|
+
config.outpath / "#{constant_name.underscore}.rbi"
|
341
|
+
end
|
342
|
+
|
280
343
|
sig { params(gem_name: String, version: String).returns(Pathname) }
|
281
344
|
def gem_rbi_filename(gem_name, version)
|
282
345
|
config.outpath / "#{gem_name}@#{version}.rbi"
|
@@ -316,6 +379,7 @@ module Tapioca
|
|
316
379
|
|
317
380
|
sig { params(filename: Pathname).void }
|
318
381
|
def remove(filename)
|
382
|
+
return unless filename.exist?
|
319
383
|
say("-- Removing: #{filename}")
|
320
384
|
filename.unlink
|
321
385
|
end
|
@@ -434,7 +498,7 @@ module Tapioca
|
|
434
498
|
rbi_body_content = compiler.compile(gem)
|
435
499
|
content = String.new
|
436
500
|
content << rbi_header(
|
437
|
-
|
501
|
+
"#{Config::DEFAULT_COMMAND} sync",
|
438
502
|
reason: "types exported from the `#{gem.name}` gem",
|
439
503
|
strictness: strictness
|
440
504
|
)
|
@@ -451,31 +515,122 @@ module Tapioca
|
|
451
515
|
end
|
452
516
|
File.write(filename.to_s, content)
|
453
517
|
|
454
|
-
Pathname.glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
518
|
+
T.unsafe(Pathname).glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
455
519
|
remove(file) unless file.basename.to_s == gem.rbi_file_name
|
456
520
|
end
|
457
521
|
end
|
458
522
|
|
459
|
-
sig
|
460
|
-
|
523
|
+
sig do
|
524
|
+
params(constant_name: String, contents: String, outpath: Pathname, quiet: T::Boolean)
|
525
|
+
.returns(T.nilable(Pathname))
|
526
|
+
end
|
527
|
+
def compile_dsl_rbi(constant_name, contents, outpath: config.outpath, quiet: false)
|
461
528
|
return if contents.nil?
|
462
529
|
|
463
|
-
command = format(config.generate_command, constant.name)
|
464
|
-
constant_name = Module.instance_method(:name).bind(constant).call
|
465
530
|
rbi_name = constant_name.underscore + ".rbi"
|
466
|
-
filename =
|
531
|
+
filename = outpath / rbi_name
|
467
532
|
|
468
533
|
out = String.new
|
469
534
|
out << rbi_header(
|
470
|
-
|
471
|
-
reason: "dynamic methods in `#{
|
535
|
+
"#{Config::DEFAULT_COMMAND} dsl #{constant_name}",
|
536
|
+
reason: "dynamic methods in `#{constant_name}`"
|
472
537
|
)
|
473
538
|
out << contents
|
474
539
|
|
475
540
|
FileUtils.mkdir_p(File.dirname(filename))
|
476
541
|
File.write(filename, out)
|
477
|
-
|
478
|
-
|
542
|
+
|
543
|
+
unless quiet
|
544
|
+
say("Wrote: ", [:green])
|
545
|
+
say(filename)
|
546
|
+
end
|
547
|
+
|
548
|
+
filename
|
549
|
+
end
|
550
|
+
|
551
|
+
sig { params(tmp_dir: Pathname).returns(T::Hash[String, Symbol]) }
|
552
|
+
def verify_dsl_rbi(tmp_dir:)
|
553
|
+
diff = {}
|
554
|
+
|
555
|
+
existing_rbis = rbi_files_in(config.outpath)
|
556
|
+
new_rbis = rbi_files_in(tmp_dir)
|
557
|
+
|
558
|
+
added_files = (new_rbis - existing_rbis)
|
559
|
+
|
560
|
+
added_files.each do |file|
|
561
|
+
diff[file] = :added
|
562
|
+
end
|
563
|
+
|
564
|
+
removed_files = (existing_rbis - new_rbis)
|
565
|
+
|
566
|
+
removed_files.each do |file|
|
567
|
+
diff[file] = :removed
|
568
|
+
end
|
569
|
+
|
570
|
+
common_files = (existing_rbis & new_rbis)
|
571
|
+
|
572
|
+
changed_files = common_files.map do |filename|
|
573
|
+
filename unless FileUtils.identical?(config.outpath / filename, tmp_dir / filename)
|
574
|
+
end.compact
|
575
|
+
|
576
|
+
changed_files.each do |file|
|
577
|
+
diff[file] = :changed
|
578
|
+
end
|
579
|
+
|
580
|
+
diff
|
581
|
+
end
|
582
|
+
|
583
|
+
sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
|
584
|
+
def build_error_for_files(cause, files)
|
585
|
+
filenames = files.map do |file|
|
586
|
+
config.outpath / file
|
587
|
+
end.join("\n - ")
|
588
|
+
|
589
|
+
" File(s) #{cause}:\n - #{filenames}"
|
590
|
+
end
|
591
|
+
|
592
|
+
sig { params(path: Pathname).returns(T::Array[Pathname]) }
|
593
|
+
def rbi_files_in(path)
|
594
|
+
Pathname.glob(path / "**/*.rbi").map do |file|
|
595
|
+
file.relative_path_from(path)
|
596
|
+
end.sort
|
597
|
+
end
|
598
|
+
|
599
|
+
sig { params(dir: Pathname, constant_lookup: T::Hash[String, String]).void }
|
600
|
+
def perform_dsl_verification(dir, constant_lookup)
|
601
|
+
diff = verify_dsl_rbi(tmp_dir: dir)
|
602
|
+
|
603
|
+
if diff.empty?
|
604
|
+
say("Nothing to do, all RBIs are up-to-date.")
|
605
|
+
else
|
606
|
+
constants = T.unsafe(constant_lookup).values_at(*diff.keys).join(" ")
|
607
|
+
|
608
|
+
say("RBI files are out-of-date, please run:")
|
609
|
+
say(" `#{Config::DEFAULT_COMMAND} dsl #{constants}`")
|
610
|
+
|
611
|
+
say("")
|
612
|
+
|
613
|
+
say("Reason:", [:red])
|
614
|
+
diff.group_by(&:last).sort.each do |cause, diff_for_cause|
|
615
|
+
say(build_error_for_files(cause, diff_for_cause.map(&:first)))
|
616
|
+
end
|
617
|
+
|
618
|
+
exit(1)
|
619
|
+
end
|
620
|
+
ensure
|
621
|
+
FileUtils.remove_entry(dir)
|
622
|
+
end
|
623
|
+
|
624
|
+
sig { params(files: T::Set[Pathname]).void }
|
625
|
+
def purge_stale_dsl_rbi_files(files)
|
626
|
+
if files.any?
|
627
|
+
say("Removing stale RBI files...")
|
628
|
+
|
629
|
+
files.sort.each do |filename|
|
630
|
+
remove(filename)
|
631
|
+
end
|
632
|
+
say("")
|
633
|
+
end
|
479
634
|
end
|
480
635
|
end
|
481
636
|
end
|
@@ -0,0 +1,219 @@
|
|
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 generic type with an overridden `name`
|
55
|
+
# method that returns the name we constructed above.
|
56
|
+
#
|
57
|
+
# Also, we try to memoize the generic type based on the name, so that
|
58
|
+
# we don't have to keep recreating them all the time.
|
59
|
+
@generic_instances[name] ||= create_generic_type(constant, name)
|
60
|
+
end
|
61
|
+
|
62
|
+
sig do
|
63
|
+
params(
|
64
|
+
constant: T.untyped,
|
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)
|
73
|
+
end
|
74
|
+
|
75
|
+
sig do
|
76
|
+
params(
|
77
|
+
constant: T.untyped,
|
78
|
+
type_template: T::Types::TypeVariable,
|
79
|
+
fixed: T.untyped,
|
80
|
+
lower: T.untyped,
|
81
|
+
upper: T.untyped
|
82
|
+
).void
|
83
|
+
end
|
84
|
+
def register_type_template(constant, type_template, fixed, lower, upper)
|
85
|
+
register_type_variable(constant, :type_template, type_template, fixed, lower, upper)
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { params(constant: Module).returns(T.nilable(T::Hash[Integer, String])) }
|
89
|
+
def lookup_type_variables(constant)
|
90
|
+
@type_variables[object_id_of(constant)]
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
sig { params(constant: Module, name: String).returns(Module) }
|
96
|
+
def create_generic_type(constant, name)
|
97
|
+
generic_type = case constant
|
98
|
+
when Class
|
99
|
+
# For classes, we want to create a subclass, so that an instance of
|
100
|
+
# the generic class `Foo[Bar]` is still a `Foo`. That is:
|
101
|
+
# `Foo[Bar].new.is_a?(Foo)` should be true, which isn't the case
|
102
|
+
# if we just clone the class. But subclassing works just fine.
|
103
|
+
create_sealed_safe_subclass(constant)
|
104
|
+
else
|
105
|
+
# This can only be a module and it is fine to just clone modules
|
106
|
+
# since they can't have instances and will not have `is_a?` relationships.
|
107
|
+
# Moreover, we never `include`/`extend` any generic modules into the
|
108
|
+
# ancestor tree, so this doesn't become a problem with checking the
|
109
|
+
# instance of a class being `is_a?` of a module type.
|
110
|
+
constant.clone
|
111
|
+
end
|
112
|
+
|
113
|
+
# Let's set the `name` method to return the proper generic name
|
114
|
+
generic_type.define_singleton_method(:name) { name }
|
115
|
+
|
116
|
+
# Return the generic type we created
|
117
|
+
generic_type
|
118
|
+
end
|
119
|
+
|
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
|
+
sig { params(constant: Class).returns(Class) }
|
154
|
+
def create_sealed_safe_subclass(constant)
|
155
|
+
# If the constant is not sealed let's just bail early.
|
156
|
+
# We just return a subclass of the constant.
|
157
|
+
return Class.new(constant) unless T::Private::Sealed.sealed_module?(constant)
|
158
|
+
|
159
|
+
# Since sealed classes can normally not be subclassed, we need to trick
|
160
|
+
# sealed classes into thinking that the generic type we are
|
161
|
+
# creating by subclassing is actually safe for sealed types.
|
162
|
+
#
|
163
|
+
# Get the filename the sealed class was declared in
|
164
|
+
decl_file = constant.instance_variable_get(:@sorbet_sealed_module_decl_file)
|
165
|
+
begin
|
166
|
+
# Clear the current declaration filename on the class
|
167
|
+
constant.remove_instance_variable(:@sorbet_sealed_module_decl_file)
|
168
|
+
# Make this file be the declaration filename so that Sorbet runtime
|
169
|
+
# does not shout at us for an invalid subclassing.
|
170
|
+
T.cast(constant, T::Helpers).sealed!
|
171
|
+
# return a subclass
|
172
|
+
Class.new(constant)
|
173
|
+
ensure
|
174
|
+
# Reinstate the original declaration filename
|
175
|
+
constant.instance_variable_set(:@sorbet_sealed_module_decl_file, decl_file)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
sig { params(constant: Module).returns(T::Hash[Integer, String]) }
|
180
|
+
def lookup_or_initialize_type_variables(constant)
|
181
|
+
@type_variables[object_id_of(constant)] ||= {}
|
182
|
+
end
|
183
|
+
|
184
|
+
sig do
|
185
|
+
params(
|
186
|
+
type_variable_type: Symbol,
|
187
|
+
variance: Symbol,
|
188
|
+
fixed: T.untyped,
|
189
|
+
lower: T.untyped,
|
190
|
+
upper: T.untyped
|
191
|
+
).returns(String)
|
192
|
+
end
|
193
|
+
def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
|
194
|
+
parts = []
|
195
|
+
parts << ":#{variance}" unless variance == :invariant
|
196
|
+
parts << "fixed: #{fixed}" if fixed
|
197
|
+
parts << "lower: #{lower}" unless lower == T.untyped
|
198
|
+
parts << "upper: #{upper}" unless upper == BasicObject
|
199
|
+
|
200
|
+
parameters = parts.join(", ")
|
201
|
+
|
202
|
+
serialized = T.let(type_variable_type.to_s, String)
|
203
|
+
serialized += "(#{parameters})" unless parameters.empty?
|
204
|
+
|
205
|
+
serialized
|
206
|
+
end
|
207
|
+
|
208
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
209
|
+
def name_of(constant)
|
210
|
+
Module.instance_method(:name).bind(constant).call
|
211
|
+
end
|
212
|
+
|
213
|
+
sig { params(object: BasicObject).returns(Integer) }
|
214
|
+
def object_id_of(object)
|
215
|
+
Object.instance_method(:object_id).bind(object).call
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|