tapioca 0.4.14 → 0.4.19
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 +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
|