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 +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 +142 -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/gemfile.rb +2 -2
- data/lib/tapioca/generator.rb +123 -24
- data/lib/tapioca/generic_type_registry.rb +193 -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/gemfile.rb
CHANGED
@@ -53,13 +53,13 @@ module Tapioca
|
|
53
53
|
|
54
54
|
sig { returns([T::Array[Gem], T::Array[String]]) }
|
55
55
|
def load_dependencies
|
56
|
-
|
56
|
+
deps = definition.locked_gems.dependencies.values
|
57
57
|
|
58
58
|
missing_specs = T::Array[String].new
|
59
59
|
|
60
60
|
dependencies = definition
|
61
61
|
.resolve
|
62
|
-
.materialize(
|
62
|
+
.materialize(deps, missing_specs)
|
63
63
|
.map { |spec| Gem.new(spec) }
|
64
64
|
.reject { |gem| gem.ignore?(dir) }
|
65
65
|
.uniq(&:rbi_file_name)
|
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,26 @@ 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
|
+
).void
|
124
|
+
end
|
125
|
+
def build_dsl(requested_constants, should_verify: false)
|
121
126
|
load_application(eager_load: requested_constants.empty?)
|
122
127
|
load_dsl_generators
|
123
128
|
|
124
|
-
|
129
|
+
if should_verify
|
130
|
+
say("Checking for out-of-date RBIs...")
|
131
|
+
else
|
132
|
+
say("Compiling DSL RBI files...")
|
133
|
+
end
|
125
134
|
say("")
|
126
135
|
|
136
|
+
outpath = should_verify ? Dir.mktmpdir : config.outpath
|
137
|
+
rbi_files_to_purge = existing_rbi_filenames(requested_constants)
|
138
|
+
|
127
139
|
compiler = Compilers::DslCompiler.new(
|
128
140
|
requested_constants: constantize(requested_constants),
|
129
141
|
requested_generators: config.generators,
|
@@ -133,14 +145,21 @@ module Tapioca
|
|
133
145
|
)
|
134
146
|
|
135
147
|
compiler.run do |constant, contents|
|
136
|
-
compile_dsl_rbi(constant, contents)
|
148
|
+
filename = compile_dsl_rbi(constant, contents, outpath: Pathname.new(outpath))
|
149
|
+
rbi_files_to_purge.delete(filename) if filename
|
137
150
|
end
|
138
|
-
|
139
151
|
say("")
|
140
|
-
say("Done", :green)
|
141
152
|
|
142
|
-
|
143
|
-
|
153
|
+
if should_verify
|
154
|
+
perform_dsl_verification(outpath)
|
155
|
+
else
|
156
|
+
purge_stale_dsl_rbi_files(rbi_files_to_purge)
|
157
|
+
|
158
|
+
say("Done", :green)
|
159
|
+
|
160
|
+
say("All operations performed in working directory.", [:green, :bold])
|
161
|
+
say("Please review changes and commit them.", [:green, :bold])
|
162
|
+
end
|
144
163
|
end
|
145
164
|
|
146
165
|
sig { void }
|
@@ -206,7 +225,7 @@ module Tapioca
|
|
206
225
|
say_error("If you populated ", :yellow)
|
207
226
|
say_error("#{file} ", :bold, :blue)
|
208
227
|
say_error("with ", :yellow)
|
209
|
-
say_error("
|
228
|
+
say_error("`#{Config::DEFAULT_COMMAND} require`", :bold, :blue)
|
210
229
|
say_error("you should probably review it and remove the faulty line.", :yellow)
|
211
230
|
end
|
212
231
|
|
@@ -253,13 +272,38 @@ module Tapioca
|
|
253
272
|
|
254
273
|
sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
|
255
274
|
def constantize(constant_names)
|
256
|
-
constant_names.map do |name|
|
275
|
+
constant_map = constant_names.map do |name|
|
257
276
|
begin
|
258
|
-
name.constantize
|
277
|
+
[name, name.constantize]
|
259
278
|
rescue NameError
|
260
|
-
nil
|
279
|
+
[name, nil]
|
261
280
|
end
|
262
|
-
end.
|
281
|
+
end.to_h
|
282
|
+
|
283
|
+
unprocessable_constants = constant_map.select { |_, v| v.nil? }
|
284
|
+
unless unprocessable_constants.empty?
|
285
|
+
unprocessable_constants.each do |name, _|
|
286
|
+
say("Error: Cannot find constant '#{name}'", :red)
|
287
|
+
remove(dsl_rbi_filename(name))
|
288
|
+
end
|
289
|
+
|
290
|
+
exit(1)
|
291
|
+
end
|
292
|
+
|
293
|
+
constant_map.values
|
294
|
+
end
|
295
|
+
|
296
|
+
sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
|
297
|
+
def existing_rbi_filenames(requested_constants, path: config.outpath)
|
298
|
+
filenames = if requested_constants.empty?
|
299
|
+
Pathname.glob(path / "**/*.rbi")
|
300
|
+
else
|
301
|
+
requested_constants.map do |constant_name|
|
302
|
+
dsl_rbi_filename(constant_name)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
filenames.to_set
|
263
307
|
end
|
264
308
|
|
265
309
|
sig { returns(T::Hash[String, String]) }
|
@@ -277,6 +321,11 @@ module Tapioca
|
|
277
321
|
.to_h
|
278
322
|
end
|
279
323
|
|
324
|
+
sig { params(constant_name: String).returns(Pathname) }
|
325
|
+
def dsl_rbi_filename(constant_name)
|
326
|
+
config.outpath / "#{constant_name.underscore}.rbi"
|
327
|
+
end
|
328
|
+
|
280
329
|
sig { params(gem_name: String, version: String).returns(Pathname) }
|
281
330
|
def gem_rbi_filename(gem_name, version)
|
282
331
|
config.outpath / "#{gem_name}@#{version}.rbi"
|
@@ -316,6 +365,7 @@ module Tapioca
|
|
316
365
|
|
317
366
|
sig { params(filename: Pathname).void }
|
318
367
|
def remove(filename)
|
368
|
+
return unless filename.exist?
|
319
369
|
say("-- Removing: #{filename}")
|
320
370
|
filename.unlink
|
321
371
|
end
|
@@ -434,7 +484,7 @@ module Tapioca
|
|
434
484
|
rbi_body_content = compiler.compile(gem)
|
435
485
|
content = String.new
|
436
486
|
content << rbi_header(
|
437
|
-
|
487
|
+
"#{Config::DEFAULT_COMMAND} sync",
|
438
488
|
reason: "types exported from the `#{gem.name}` gem",
|
439
489
|
strictness: strictness
|
440
490
|
)
|
@@ -451,23 +501,22 @@ module Tapioca
|
|
451
501
|
end
|
452
502
|
File.write(filename.to_s, content)
|
453
503
|
|
454
|
-
Pathname.glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
504
|
+
T.unsafe(Pathname).glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
455
505
|
remove(file) unless file.basename.to_s == gem.rbi_file_name
|
456
506
|
end
|
457
507
|
end
|
458
508
|
|
459
|
-
sig { params(constant: Module, contents: String).
|
460
|
-
def compile_dsl_rbi(constant, contents)
|
509
|
+
sig { params(constant: Module, contents: String, outpath: Pathname).returns(T.nilable(Pathname)) }
|
510
|
+
def compile_dsl_rbi(constant, contents, outpath: config.outpath)
|
461
511
|
return if contents.nil?
|
462
512
|
|
463
|
-
command = format(config.generate_command, constant.name)
|
464
513
|
constant_name = Module.instance_method(:name).bind(constant).call
|
465
514
|
rbi_name = constant_name.underscore + ".rbi"
|
466
|
-
filename =
|
515
|
+
filename = outpath / rbi_name
|
467
516
|
|
468
517
|
out = String.new
|
469
518
|
out << rbi_header(
|
470
|
-
|
519
|
+
"#{Config::DEFAULT_COMMAND} dsl #{constant_name}",
|
471
520
|
reason: "dynamic methods in `#{constant.name}`"
|
472
521
|
)
|
473
522
|
out << contents
|
@@ -476,6 +525,56 @@ module Tapioca
|
|
476
525
|
File.write(filename, out)
|
477
526
|
say("Wrote: ", [:green])
|
478
527
|
say(filename)
|
528
|
+
|
529
|
+
filename
|
530
|
+
end
|
531
|
+
|
532
|
+
sig { params(tmp_dir: Pathname).returns(T.nilable(String)) }
|
533
|
+
def verify_dsl_rbi(tmp_dir:)
|
534
|
+
existing_rbis = existing_rbi_filenames([]).sort
|
535
|
+
new_rbis = existing_rbi_filenames([], path: tmp_dir).grep_v(/gem|shim/).sort
|
536
|
+
|
537
|
+
return "New file(s) introduced." if existing_rbis.length != new_rbis.length
|
538
|
+
|
539
|
+
desynced_files = []
|
540
|
+
|
541
|
+
(0..existing_rbis.length - 1).each do |i|
|
542
|
+
desynced_files << new_rbis[i] unless FileUtils.identical?(existing_rbis[i], new_rbis[i])
|
543
|
+
end
|
544
|
+
|
545
|
+
unless desynced_files.empty?
|
546
|
+
filenames = desynced_files.map { |f| f.to_s.sub!(tmp_dir.to_s, "sorbet/rbi/dsl") }.join("\n - ")
|
547
|
+
|
548
|
+
return "File(s) updated:\n - #{filenames}"
|
549
|
+
end
|
550
|
+
|
551
|
+
nil
|
552
|
+
end
|
553
|
+
|
554
|
+
sig { params(dir: String).void }
|
555
|
+
def perform_dsl_verification(dir)
|
556
|
+
if (error = verify_dsl_rbi(tmp_dir: Pathname.new(dir)))
|
557
|
+
say("RBI files are out-of-date, please run `#{Config::DEFAULT_COMMAND} dsl` to update.")
|
558
|
+
say("Reason: ", [:red])
|
559
|
+
say(error)
|
560
|
+
exit(1)
|
561
|
+
else
|
562
|
+
say("Nothing to do, all RBIs are up-to-date.")
|
563
|
+
end
|
564
|
+
ensure
|
565
|
+
FileUtils.remove_entry(dir)
|
566
|
+
end
|
567
|
+
|
568
|
+
sig { params(files: T::Set[Pathname]).void }
|
569
|
+
def purge_stale_dsl_rbi_files(files)
|
570
|
+
if files.any?
|
571
|
+
say("Removing stale RBI files...")
|
572
|
+
|
573
|
+
files.sort.each do |filename|
|
574
|
+
remove(filename)
|
575
|
+
end
|
576
|
+
say("")
|
577
|
+
end
|
479
578
|
end
|
480
579
|
end
|
481
580
|
end
|
@@ -0,0 +1,193 @@
|
|
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
|
+
Class.new(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: Module).returns(T::Hash[Integer, String]) }
|
154
|
+
def lookup_or_initialize_type_variables(constant)
|
155
|
+
@type_variables[object_id_of(constant)] ||= {}
|
156
|
+
end
|
157
|
+
|
158
|
+
sig do
|
159
|
+
params(
|
160
|
+
type_variable_type: Symbol,
|
161
|
+
variance: Symbol,
|
162
|
+
fixed: T.untyped,
|
163
|
+
lower: T.untyped,
|
164
|
+
upper: T.untyped
|
165
|
+
).returns(String)
|
166
|
+
end
|
167
|
+
def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
|
168
|
+
parts = []
|
169
|
+
parts << ":#{variance}" unless variance == :invariant
|
170
|
+
parts << "fixed: #{fixed}" if fixed
|
171
|
+
parts << "lower: #{lower}" unless lower == T.untyped
|
172
|
+
parts << "upper: #{upper}" unless upper == BasicObject
|
173
|
+
|
174
|
+
parameters = parts.join(", ")
|
175
|
+
|
176
|
+
serialized = T.let(type_variable_type.to_s, String)
|
177
|
+
serialized += "(#{parameters})" unless parameters.empty?
|
178
|
+
|
179
|
+
serialized
|
180
|
+
end
|
181
|
+
|
182
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
183
|
+
def name_of(constant)
|
184
|
+
Module.instance_method(:name).bind(constant).call
|
185
|
+
end
|
186
|
+
|
187
|
+
sig { params(object: BasicObject).returns(Integer) }
|
188
|
+
def object_id_of(object)
|
189
|
+
Object.instance_method(:object_id).bind(object).call
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|