tapioca 0.6.1 → 0.6.2
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 +5 -0
- data/README.md +56 -14
- data/lib/tapioca/cli.rb +1 -0
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +1 -1
- data/lib/tapioca/compilers/dsl/base.rb +10 -3
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +30 -5
- data/lib/tapioca/compilers/requires_compiler.rb +4 -11
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +26 -26
- data/lib/tapioca/gemfile.rb +44 -20
- data/lib/tapioca/generators/base.rb +1 -1
- data/lib/tapioca/generic_type_registry.rb +22 -7
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +12 -2
- data/lib/tapioca/helpers/cli_helper.rb +10 -9
- data/lib/tapioca/helpers/config_helper.rb +116 -0
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +92 -54
- data/lib/tapioca/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df803cbfcb6a400e88175b21c0bfd3d5c8089853d5ca09cf8f7b7d0076ce201f
|
4
|
+
data.tar.gz: fc580b1ae8202f4d94a45d75665c07a072c7ffde6652aaa3580dd1e8a196288c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a36a8f3dc321ce2cf8f5402f3b00aee7ef9e6ea2748ee95371b64dceec217df218fd8dc6ca786cfce90478c9c0db7c14bde1f43276f76a65b4ed2d6fa7093672
|
7
|
+
data.tar.gz: f688b633d7e0ed4627a5929aef8293619641609c143c43082b291330f9fe5fb2c5a72e9e70be0f2d57274908b4f1fc48dd6ff8dcb4d7d815c894af9567a21417
|
data/Gemfile
CHANGED
@@ -36,4 +36,9 @@ group(:development, :test) do
|
|
36
36
|
gem("aasm", require: false)
|
37
37
|
gem("bcrypt", require: false)
|
38
38
|
gem("xpath", require: false)
|
39
|
+
|
40
|
+
# net-smtp was removed from default gems in Ruby 3.1, but is used by the `mail` gem.
|
41
|
+
# So we need to add it as a dependency until `mail` is fixed:
|
42
|
+
# https://github.com/rails/rails/blob/0919aa97260ab8240150278d3b07a1547489e3fd/Gemfile#L178-L191
|
43
|
+
gem("net-smtp", "0.3.1", require: false)
|
39
44
|
end
|
data/README.md
CHANGED
@@ -80,8 +80,8 @@ Command: `tapioca init`
|
|
80
80
|
|
81
81
|
This will create the `sorbet/config` and `sorbet/tapioca/require.rb` files for you, if they don't exist. If any of the files already exist, they will not be changed.
|
82
82
|
|
83
|
+
<!-- START_HELP_COMMAND_INIT -->
|
83
84
|
```shell
|
84
|
-
$ bundle exec tapioca help init
|
85
85
|
Usage:
|
86
86
|
tapioca init
|
87
87
|
|
@@ -92,6 +92,7 @@ Options:
|
|
92
92
|
|
93
93
|
initializes folder structure
|
94
94
|
```
|
95
|
+
<!-- END_HELP_COMMAND_INIT -->
|
95
96
|
|
96
97
|
### Generate RBI files for gems
|
97
98
|
|
@@ -99,13 +100,13 @@ Command: `tapioca gem [gems...]`
|
|
99
100
|
|
100
101
|
This will generate RBIs for the specified gems and place them in the RBI directory.
|
101
102
|
|
103
|
+
<!-- START_HELP_COMMAND_GEM -->
|
102
104
|
```shell
|
103
|
-
$ bundle exec tapioca help gem
|
104
105
|
Usage:
|
105
106
|
tapioca gem [gem...]
|
106
107
|
|
107
108
|
Options:
|
108
|
-
--out, -o, [--outdir=directory] # The output directory for generated RBI files
|
109
|
+
--out, -o, [--outdir=directory] # The output directory for generated gem RBI files
|
109
110
|
# Default: sorbet/rbi/gems
|
110
111
|
[--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
111
112
|
# Default: true
|
@@ -113,10 +114,10 @@ Options:
|
|
113
114
|
--pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
|
114
115
|
--post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
|
115
116
|
# Default: sorbet/tapioca/require.rb
|
116
|
-
-x, [--exclude=gem [gem ...]] #
|
117
|
-
--typed, -t, [--typed-overrides=gem:level [gem:level ...]] #
|
117
|
+
-x, [--exclude=gem [gem ...]] # Exclude the given gem(s) from RBI generation
|
118
|
+
--typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Override for typed sigils for generated gem RBIs
|
118
119
|
# Default: {"activesupport"=>"false"}
|
119
|
-
[--verify], [--no-verify] #
|
120
|
+
[--verify], [--no-verify] # Verify RBIs are up-to-date
|
120
121
|
[--doc], [--no-doc] # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
|
121
122
|
[--exported-gem-rbis], [--no-exported-gem-rbis] # Include RBIs found in the `rbi/` directory of the gem
|
122
123
|
# Default: true
|
@@ -128,6 +129,7 @@ Options:
|
|
128
129
|
|
129
130
|
generate RBIs from gems
|
130
131
|
```
|
132
|
+
<!-- END_HELP_COMMAND_GEM -->
|
131
133
|
|
132
134
|
### Generate the list of all unresolved constants
|
133
135
|
|
@@ -135,13 +137,13 @@ Command: `tapioca todo`
|
|
135
137
|
|
136
138
|
This will generate the file `sorbet/rbi/todo.rbi` defining all unresolved constants as empty modules.
|
137
139
|
|
140
|
+
<!-- START_HELP_COMMAND_TODO -->
|
138
141
|
```shell
|
139
|
-
$ bundle exec tapioca help todo
|
140
142
|
Usage:
|
141
143
|
tapioca todo
|
142
144
|
|
143
145
|
Options:
|
144
|
-
[--todo-file=TODO_FILE]
|
146
|
+
[--todo-file=TODO_FILE] # Path to the generated todo RBI file
|
145
147
|
# Default: sorbet/rbi/todo.rbi
|
146
148
|
[--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
147
149
|
# Default: true
|
@@ -151,6 +153,7 @@ Options:
|
|
151
153
|
|
152
154
|
generate the list of unresolved constants
|
153
155
|
```
|
156
|
+
<!-- END_HELP_COMMAND_TODO -->
|
154
157
|
|
155
158
|
### Generate DSL RBI files
|
156
159
|
|
@@ -158,18 +161,18 @@ Command: `tapioca dsl [constant...]`
|
|
158
161
|
|
159
162
|
This will generate DSL RBIs for specified constants (or for all handled constants, if a constant name is not supplied). You can read about DSL RBI generators supplied by `tapioca` in [the manual](manual/generators.md).
|
160
163
|
|
164
|
+
<!-- START_HELP_COMMAND_DSL -->
|
161
165
|
```shell
|
162
|
-
$ bundle exec tapioca help dsl
|
163
166
|
Usage:
|
164
167
|
tapioca dsl [constant...]
|
165
168
|
|
166
169
|
Options:
|
167
|
-
--out, -o, [--outdir=directory] # The output directory for generated RBI files
|
170
|
+
--out, -o, [--outdir=directory] # The output directory for generated DSL RBI files
|
168
171
|
# Default: sorbet/rbi/dsl
|
169
172
|
[--file-header], [--no-file-header] # Add a "This file is generated" header on top of each generated RBI file
|
170
173
|
# Default: true
|
171
|
-
[--only=generator [generator ...]] # Only run supplied DSL
|
172
|
-
[--exclude=generator [generator ...]] # Exclude supplied DSL
|
174
|
+
[--only=generator [generator ...]] # Only run supplied DSL generator(s)
|
175
|
+
[--exclude=generator [generator ...]] # Exclude supplied DSL generator(s)
|
173
176
|
[--verify], [--no-verify] # Verifies RBIs are up-to-date
|
174
177
|
-q, [--quiet], [--no-quiet] # Supresses file creation output
|
175
178
|
-w, [--workers=N] # EXPERIMENTAL: Number of parallel workers to use when generating RBIs
|
@@ -180,6 +183,8 @@ Options:
|
|
180
183
|
|
181
184
|
generate RBIs for dynamic methods
|
182
185
|
```
|
186
|
+
<!-- END_HELP_COMMAND_DSL -->
|
187
|
+
|
183
188
|
## Configuration
|
184
189
|
|
185
190
|
Tapioca supports loading command defaults from a configuration file. The default configuration
|
@@ -192,20 +197,57 @@ For example, if you always want to generate gem RBIs with inline documentation,
|
|
192
197
|
|
193
198
|
```yaml
|
194
199
|
gem:
|
195
|
-
|
200
|
+
doc: true
|
196
201
|
```
|
197
202
|
|
198
203
|
Additionally, if you always want to exclude the `AASM` and `ActiveRecordFixtures` DSL compilers in your DSL RBI generation runs, your config file would then look like this:
|
199
204
|
|
200
205
|
```yaml
|
201
206
|
gem:
|
202
|
-
|
207
|
+
doc: true
|
203
208
|
dsl:
|
204
209
|
exclude:
|
205
210
|
- UrlHelpers
|
206
211
|
- ActiveRecordFixtures
|
207
212
|
```
|
208
213
|
|
214
|
+
The full configuration file, with each option and its default value, would look something like this:
|
215
|
+
<!-- START_CONFIG_TEMPLATE -->
|
216
|
+
```yaml
|
217
|
+
---
|
218
|
+
require:
|
219
|
+
postrequire: sorbet/tapioca/require.rb
|
220
|
+
todo:
|
221
|
+
todo_file: sorbet/rbi/todo.rbi
|
222
|
+
file_header: true
|
223
|
+
dsl:
|
224
|
+
outdir: sorbet/rbi/dsl
|
225
|
+
file_header: true
|
226
|
+
only: []
|
227
|
+
exclude: []
|
228
|
+
verify: false
|
229
|
+
quiet: false
|
230
|
+
workers: 1
|
231
|
+
gem:
|
232
|
+
outdir: sorbet/rbi/gems
|
233
|
+
file_header: true
|
234
|
+
all: false
|
235
|
+
prerequire: ''
|
236
|
+
postrequire: sorbet/tapioca/require.rb
|
237
|
+
exclude: []
|
238
|
+
typed_overrides:
|
239
|
+
activesupport: 'false'
|
240
|
+
verify: false
|
241
|
+
doc: false
|
242
|
+
exported_gem_rbis: true
|
243
|
+
workers: 1
|
244
|
+
clean_shims:
|
245
|
+
gem_rbi_dir: sorbet/rbi/gems
|
246
|
+
dsl_rbi_dir: sorbet/rbi/dsl
|
247
|
+
shim_rbi_dir: sorbet/rbi/shims
|
248
|
+
```
|
249
|
+
<!-- END_CONFIG_TEMPLATE -->
|
250
|
+
|
209
251
|
## Contributing
|
210
252
|
|
211
253
|
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/tapioca. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://github.com/Shopify/tapioca/blob/main/CODE_OF_CONDUCT.md) code of conduct.
|
data/lib/tapioca/cli.rb
CHANGED
@@ -296,7 +296,7 @@ module Tapioca
|
|
296
296
|
|
297
297
|
sig { params(type: String).returns(String) }
|
298
298
|
def as_nilable_type(type)
|
299
|
-
return type if type.start_with?("T.nilable(")
|
299
|
+
return type if type.start_with?("T.nilable(") || type == "T.untyped"
|
300
300
|
"T.nilable(#{type})"
|
301
301
|
end
|
302
302
|
end
|
@@ -136,11 +136,13 @@ module Tapioca
|
|
136
136
|
method_def = signature.nil? ? method_def : signature.method
|
137
137
|
method_types = parameters_types_from_signature(method_def, signature)
|
138
138
|
|
139
|
-
|
139
|
+
parameters = T.let(method_def.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
|
140
|
+
|
141
|
+
parameters.each_with_index.map do |(type, name), index|
|
140
142
|
fallback_arg_name = "_arg#{index}"
|
141
143
|
|
142
|
-
name
|
143
|
-
name =
|
144
|
+
name = name ? name.to_s : fallback_arg_name
|
145
|
+
name = fallback_arg_name unless valid_parameter_name?(name)
|
144
146
|
method_type = T.must(method_types[index])
|
145
147
|
|
146
148
|
case type
|
@@ -173,6 +175,11 @@ module Tapioca
|
|
173
175
|
return_type = "T.untyped" if return_type == "<NOT-TYPED>"
|
174
176
|
return_type
|
175
177
|
end
|
178
|
+
|
179
|
+
sig { params(name: String).returns(T::Boolean) }
|
180
|
+
def valid_parameter_name?(name)
|
181
|
+
name.match?(/^[[[:alnum:]]_]+$/)
|
182
|
+
end
|
176
183
|
end
|
177
184
|
end
|
178
185
|
end
|
@@ -174,20 +174,31 @@ class DynamicMixinCompiler
|
|
174
174
|
|
175
175
|
sig { params(tree: RBI::Tree).returns([T::Array[Module], T::Array[Module]]) }
|
176
176
|
def compile_mixes_in_class_methods(tree)
|
177
|
-
includes = dynamic_includes.
|
178
|
-
includes.each do |mod|
|
177
|
+
includes = dynamic_includes.map do |mod|
|
179
178
|
qname = qualified_name_of(mod)
|
180
|
-
|
181
|
-
|
179
|
+
|
180
|
+
next if qname.nil? || qname.empty?
|
181
|
+
next if filtered_mixin?(qname)
|
182
|
+
|
183
|
+
tree << RBI::Include.new(qname)
|
184
|
+
|
185
|
+
mod
|
186
|
+
end.compact
|
182
187
|
|
183
188
|
# If we can generate multiple mixes_in_class_methods, then we want to use all dynamic extends that are not the
|
184
189
|
# constant itself
|
185
|
-
mixed_in_class_methods = dynamic_extends.select
|
190
|
+
mixed_in_class_methods = dynamic_extends.select do |mod|
|
191
|
+
mod != @constant && !module_included_by_another_dynamic_extend?(mod, dynamic_extends)
|
192
|
+
end
|
193
|
+
|
186
194
|
return [[], []] if mixed_in_class_methods.empty?
|
187
195
|
|
188
196
|
mixed_in_class_methods.each do |mod|
|
189
197
|
qualified_name = qualified_name_of(mod)
|
198
|
+
|
190
199
|
next if qualified_name.nil? || qualified_name.empty?
|
200
|
+
next if filtered_mixin?(qualified_name)
|
201
|
+
|
191
202
|
tree << RBI::MixesInClassMethods.new(qualified_name)
|
192
203
|
end
|
193
204
|
|
@@ -195,4 +206,18 @@ class DynamicMixinCompiler
|
|
195
206
|
rescue
|
196
207
|
[[], []] # silence errors
|
197
208
|
end
|
209
|
+
|
210
|
+
sig { params(mod: Module, dynamic_extends: T::Array[Module]).returns(T::Boolean) }
|
211
|
+
def module_included_by_another_dynamic_extend?(mod, dynamic_extends)
|
212
|
+
dynamic_extends.any? do |dynamic_extend|
|
213
|
+
mod != dynamic_extend && ancestors_of(dynamic_extend).include?(mod)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
sig { params(qualified_mixin_name: String).returns(T::Boolean) }
|
218
|
+
def filtered_mixin?(qualified_mixin_name)
|
219
|
+
# filter T:: namespace mixins that aren't T::Props
|
220
|
+
# T::Props and subconstants have semantic value
|
221
|
+
qualified_mixin_name.start_with?("::T::") && !qualified_mixin_name.start_with?("::T::Props")
|
222
|
+
end
|
198
223
|
end
|
@@ -17,10 +17,9 @@ module Tapioca
|
|
17
17
|
def compile
|
18
18
|
config = Spoom::Sorbet::Config.parse_file(@sorbet_path)
|
19
19
|
files = collect_files(config)
|
20
|
+
names_in_project = files.map { |file| [File.basename(file, ".rb"), true] }.to_h
|
20
21
|
files.flat_map do |file|
|
21
|
-
collect_requires(file).reject
|
22
|
-
name_in_project?(files, req)
|
23
|
-
end
|
22
|
+
collect_requires(file).reject { |req| names_in_project[req] }
|
24
23
|
end.sort.uniq.map do |name|
|
25
24
|
"require \"#{name}\"\n"
|
26
25
|
end.join
|
@@ -45,9 +44,10 @@ module Tapioca
|
|
45
44
|
|
46
45
|
sig { params(file_path: String).returns(T::Enumerable[String]) }
|
47
46
|
def collect_requires(file_path)
|
48
|
-
File.
|
47
|
+
File.binread(file_path).lines.map do |line|
|
49
48
|
/^\s*require\s*(\(\s*)?['"](?<name>[^'"]+)['"](\s*\))?/.match(line) { |m| m["name"] }
|
50
49
|
end.compact
|
50
|
+
.reject { |require| require.include?('#{') } # ignore interpolation
|
51
51
|
end
|
52
52
|
|
53
53
|
sig { params(config: Spoom::Sorbet::Config, file_path: Pathname).returns(T::Boolean) }
|
@@ -83,13 +83,6 @@ module Tapioca
|
|
83
83
|
def path_parts(path)
|
84
84
|
T.unsafe(path).descend.map { |part| part.basename.to_s }
|
85
85
|
end
|
86
|
-
|
87
|
-
sig { params(files: T::Enumerable[String], name: String).returns(T::Boolean) }
|
88
|
-
def name_in_project?(files, name)
|
89
|
-
files.any? do |file|
|
90
|
-
File.basename(file, ".rb") == name
|
91
|
-
end
|
92
|
-
end
|
93
86
|
end
|
94
87
|
end
|
95
88
|
end
|
@@ -141,7 +141,6 @@ module Tapioca
|
|
141
141
|
return if symbol_ignored?(name)
|
142
142
|
|
143
143
|
klass = class_of(value)
|
144
|
-
return if klass == TypeMember || klass == TypeTemplate
|
145
144
|
|
146
145
|
klass_name = if klass == ObjectSpace::WeakMap
|
147
146
|
# WeakMap is an implicit generic with one type variable
|
@@ -172,6 +171,7 @@ module Tapioca
|
|
172
171
|
sig { params(tree: RBI::Tree, name: String, constant: Module).void }
|
173
172
|
def compile_module(tree, name, constant)
|
174
173
|
return unless defined_in_gem?(constant, strict: false)
|
174
|
+
return if Tapioca::TypeVariableModule === constant
|
175
175
|
|
176
176
|
comments = documentation_comments(name)
|
177
177
|
scope =
|
@@ -282,30 +282,16 @@ module Tapioca
|
|
282
282
|
type_variables = GenericTypeRegistry.lookup_type_variables(constant)
|
283
283
|
return unless type_variables
|
284
284
|
|
285
|
-
# Create a map of subconstants (via their object ids) to their names.
|
286
|
-
# We need this later when we want to lookup the name of the registered type
|
287
|
-
# variable via the value of the type variable constant.
|
288
|
-
subconstant_to_name_lookup = constants_of(constant)
|
289
|
-
.each_with_object({}.compare_by_identity) do |constant_name, table|
|
290
|
-
table[constantize(constant_name.to_s, namespace: constant)] = constant_name.to_s
|
291
|
-
end
|
292
|
-
|
293
285
|
# Map each type variable to its string representation.
|
294
286
|
#
|
295
|
-
# Each entry of `type_variables` maps
|
287
|
+
# Each entry of `type_variables` maps a Module to a String,
|
296
288
|
# and the order they are inserted into the hash is the order they should be
|
297
289
|
# defined in the source code.
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
constant_name = subconstant_to_name_lookup[type_variable]
|
304
|
-
type_variable.name = constant_name
|
305
|
-
# Here, we know that constant_value will be an instance of
|
306
|
-
# T::Types::CustomTypeVariable, which knows how to serialize
|
307
|
-
# itself to a type_member/type_template
|
308
|
-
tree << RBI::TypeMember.new(constant_name, serialized_type_variable)
|
290
|
+
type_variable_declarations = type_variables.map do |type_variable|
|
291
|
+
type_variable_name = type_variable.name
|
292
|
+
next unless type_variable_name
|
293
|
+
|
294
|
+
tree << RBI::TypeMember.new(type_variable_name, type_variable.serialize)
|
309
295
|
end
|
310
296
|
|
311
297
|
return if type_variable_declarations.empty?
|
@@ -392,7 +378,7 @@ module Tapioca
|
|
392
378
|
.select do |mod|
|
393
379
|
name = name_of(mod)
|
394
380
|
|
395
|
-
name && !
|
381
|
+
name && !filtered_mixin?(name)
|
396
382
|
end
|
397
383
|
.map do |mod|
|
398
384
|
add_to_symbol_queue(name_of(mod))
|
@@ -496,7 +482,9 @@ module Tapioca
|
|
496
482
|
sanitized_parameters = parameters.each_with_index.map do |(type, name), index|
|
497
483
|
fallback_arg_name = "_arg#{index}"
|
498
484
|
|
499
|
-
|
485
|
+
name = if name
|
486
|
+
name.to_s
|
487
|
+
else
|
500
488
|
# For attr_writer methods, Sorbet signatures have the name
|
501
489
|
# of the method (without the trailing = sign) as the name of
|
502
490
|
# the only parameter. So, if the parameter does not have a name
|
@@ -512,15 +500,15 @@ module Tapioca
|
|
512
500
|
method_name[-1] == "="
|
513
501
|
)
|
514
502
|
|
515
|
-
|
516
|
-
|
503
|
+
if writer_method_with_sig
|
504
|
+
method_name.delete_suffix("=")
|
517
505
|
else
|
518
506
|
fallback_arg_name
|
519
507
|
end
|
520
508
|
end
|
521
509
|
|
522
510
|
# Sanitize param names
|
523
|
-
name = name
|
511
|
+
name = fallback_arg_name unless valid_parameter_name?(name)
|
524
512
|
|
525
513
|
[type, name]
|
526
514
|
end
|
@@ -613,6 +601,13 @@ module Tapioca
|
|
613
601
|
SymbolLoader.ignore_symbol?(symbol_name)
|
614
602
|
end
|
615
603
|
|
604
|
+
sig { params(mixin_name: String).returns(T::Boolean) }
|
605
|
+
def filtered_mixin?(mixin_name)
|
606
|
+
# filter T:: namespace mixins that aren't T::Props
|
607
|
+
# T::Props and subconstants have semantic value
|
608
|
+
mixin_name.start_with?("T::") && !mixin_name.start_with?("T::Props")
|
609
|
+
end
|
610
|
+
|
616
611
|
SPECIAL_METHOD_NAMES = T.let([
|
617
612
|
"!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^",
|
618
613
|
"<", "<=", "=>", ">", ">=", "==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"
|
@@ -624,6 +619,11 @@ module Tapioca
|
|
624
619
|
!!name.match(/^[[:word:]]+[?!=]?$/)
|
625
620
|
end
|
626
621
|
|
622
|
+
sig { params(name: String).returns(T::Boolean) }
|
623
|
+
def valid_parameter_name?(name)
|
624
|
+
name.match?(/^[[[:alnum:]]_]+$/)
|
625
|
+
end
|
626
|
+
|
627
627
|
sig { params(method: UnboundMethod).returns(T::Boolean) }
|
628
628
|
def method_in_gem?(method)
|
629
629
|
source_location = method.source_location&.first
|
data/lib/tapioca/gemfile.rb
CHANGED
@@ -99,6 +99,9 @@ module Tapioca
|
|
99
99
|
sig { returns(String) }
|
100
100
|
attr_reader :full_gem_path, :version
|
101
101
|
|
102
|
+
sig { returns(T::Array[Pathname]) }
|
103
|
+
attr_reader :files
|
104
|
+
|
102
105
|
sig { params(spec: Spec).void }
|
103
106
|
def initialize(spec)
|
104
107
|
@spec = T.let(spec, Tapioca::Gemfile::Spec)
|
@@ -106,6 +109,7 @@ module Tapioca
|
|
106
109
|
@full_gem_path = T.let(real_gem_path, String)
|
107
110
|
@version = T.let(version_string, String)
|
108
111
|
@exported_rbi_files = T.let(nil, T.nilable(T::Array[String]))
|
112
|
+
@files = T.let(collect_files, T::Array[Pathname])
|
109
113
|
end
|
110
114
|
|
111
115
|
sig { params(gemfile_dir: String).returns(T::Boolean) }
|
@@ -113,21 +117,6 @@ module Tapioca
|
|
113
117
|
gem_ignored? || gem_in_app_dir?(gemfile_dir)
|
114
118
|
end
|
115
119
|
|
116
|
-
sig { returns(T::Array[Pathname]) }
|
117
|
-
def files
|
118
|
-
if default_gem?
|
119
|
-
# `Bundler::RemoteSpecification` delegates missing methods to
|
120
|
-
# `Gem::Specification`, so `files` actually always exists on spec.
|
121
|
-
T.unsafe(@spec).files.map do |file|
|
122
|
-
ruby_lib_dir.join(file)
|
123
|
-
end
|
124
|
-
else
|
125
|
-
@spec.full_require_paths.flat_map do |path|
|
126
|
-
Pathname.glob((Pathname.new(path) / "**/*.rb").to_s)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
120
|
sig { returns(String) }
|
132
121
|
def name
|
133
122
|
@spec.name
|
@@ -154,7 +143,7 @@ module Tapioca
|
|
154
143
|
|
155
144
|
sig { returns(T::Array[String]) }
|
156
145
|
def exported_rbi_files
|
157
|
-
@exported_rbi_files ||= Dir.glob("#{full_gem_path}/rbi/**/*.rbi")
|
146
|
+
@exported_rbi_files ||= Dir.glob("#{full_gem_path}/rbi/**/*.rbi").sort
|
158
147
|
end
|
159
148
|
|
160
149
|
sig { returns(T::Boolean) }
|
@@ -176,14 +165,49 @@ module Tapioca
|
|
176
165
|
|
177
166
|
private
|
178
167
|
|
179
|
-
sig { returns(T::
|
168
|
+
sig { returns(T::Array[Pathname]) }
|
169
|
+
def collect_files
|
170
|
+
if default_gem?
|
171
|
+
# `Bundler::RemoteSpecification` delegates missing methods to
|
172
|
+
# `Gem::Specification`, so `files` actually always exists on spec.
|
173
|
+
T.unsafe(@spec).files.map do |file|
|
174
|
+
resolve_to_ruby_lib_dir(file)
|
175
|
+
end
|
176
|
+
else
|
177
|
+
@spec.full_require_paths.flat_map do |path|
|
178
|
+
Pathname.glob((Pathname.new(path) / "**/*.rb").to_s)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
sig { returns(T.nilable(T::Boolean)) }
|
180
184
|
def default_gem?
|
181
185
|
@spec.respond_to?(:default_gem?) && @spec.default_gem?
|
182
186
|
end
|
183
187
|
|
184
|
-
sig { returns(
|
185
|
-
def
|
186
|
-
|
188
|
+
sig { returns(Regexp) }
|
189
|
+
def require_paths_prefix_matcher
|
190
|
+
@require_paths_prefix_matcher = T.let(@require_paths_prefix_matcher, T.nilable(Regexp))
|
191
|
+
|
192
|
+
@require_paths_prefix_matcher ||= begin
|
193
|
+
require_paths = T.unsafe(@spec).require_paths
|
194
|
+
prefix_matchers = require_paths.map { |rp| Regexp.new("^#{rp}/") }
|
195
|
+
Regexp.union(prefix_matchers)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
sig { params(file: String).returns(Pathname) }
|
200
|
+
def resolve_to_ruby_lib_dir(file)
|
201
|
+
# We want to match require prefixes but fallback to an empty match
|
202
|
+
# if none of the require prefixes actually match. This is so that
|
203
|
+
# we can always replace the match with the Ruby lib directory and
|
204
|
+
# we would have properly resolved the file under the Ruby lib dir.
|
205
|
+
prefix_matcher = Regexp.union(require_paths_prefix_matcher, //)
|
206
|
+
|
207
|
+
ruby_lib_dir = RbConfig::CONFIG["rubylibdir"]
|
208
|
+
file = file.sub(prefix_matcher, "#{ruby_lib_dir}/")
|
209
|
+
|
210
|
+
Pathname.new(file).expand_path
|
187
211
|
end
|
188
212
|
|
189
213
|
sig { returns(String) }
|
@@ -20,7 +20,6 @@ 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) }
|
24
23
|
@generic_instances = T.let(
|
25
24
|
{},
|
26
25
|
T::Hash[String, Module]
|
@@ -28,7 +27,7 @@ module Tapioca
|
|
28
27
|
|
29
28
|
@type_variables = T.let(
|
30
29
|
{}.compare_by_identity,
|
31
|
-
T::Hash[Module, T::
|
30
|
+
T::Hash[Module, T::Array[TypeVariableModule]]
|
32
31
|
)
|
33
32
|
|
34
33
|
class << self
|
@@ -60,7 +59,12 @@ module Tapioca
|
|
60
59
|
@generic_instances[name] ||= create_generic_type(constant, name)
|
61
60
|
end
|
62
61
|
|
63
|
-
sig { params(
|
62
|
+
sig { params(instance: Object).returns(T::Boolean) }
|
63
|
+
def generic_type_instance?(instance)
|
64
|
+
@generic_instances.values.any? { |generic_type| generic_type === instance }
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { params(constant: Module).returns(T.nilable(T::Array[TypeVariableModule])) }
|
64
68
|
def lookup_type_variables(constant)
|
65
69
|
@type_variables[constant]
|
66
70
|
end
|
@@ -77,13 +81,13 @@ module Tapioca
|
|
77
81
|
sig do
|
78
82
|
params(
|
79
83
|
constant: T.untyped,
|
80
|
-
type_variable:
|
84
|
+
type_variable: TypeVariableModule,
|
81
85
|
).void
|
82
86
|
end
|
83
87
|
def register_type_variable(constant, type_variable)
|
84
88
|
type_variables = lookup_or_initialize_type_variables(constant)
|
85
89
|
|
86
|
-
type_variables
|
90
|
+
type_variables << type_variable
|
87
91
|
end
|
88
92
|
|
89
93
|
private
|
@@ -109,6 +113,17 @@ module Tapioca
|
|
109
113
|
# Let's set the `name` method to return the proper generic name
|
110
114
|
generic_type.define_singleton_method(:name) { name }
|
111
115
|
|
116
|
+
# We need to define a `<=` method on the cloned constant, so that Sorbet
|
117
|
+
# can do covariance/contravariance checks on the type variables.
|
118
|
+
#
|
119
|
+
# Normally, we would be doing proper covariance/contravariance checks here, but
|
120
|
+
# that is not necessary, since we are not implementing a runtime type checker
|
121
|
+
# here. It is just enough for the checks to pass, so that we can serialize the
|
122
|
+
# signatures, assuming the sigs were well-formed.
|
123
|
+
#
|
124
|
+
# So we act like all subtype checks pass.
|
125
|
+
generic_type.define_singleton_method(:<=) { |_| true }
|
126
|
+
|
112
127
|
# Return the generic type we created
|
113
128
|
generic_type
|
114
129
|
end
|
@@ -140,9 +155,9 @@ module Tapioca
|
|
140
155
|
end
|
141
156
|
end
|
142
157
|
|
143
|
-
sig { params(constant: Module).returns(T::
|
158
|
+
sig { params(constant: Module).returns(T::Array[TypeVariableModule]) }
|
144
159
|
def lookup_or_initialize_type_variables(constant)
|
145
|
-
@type_variables[constant] ||=
|
160
|
+
@type_variables[constant] ||= []
|
146
161
|
end
|
147
162
|
end
|
148
163
|
end
|
@@ -43,13 +43,13 @@ class ActiveRecordColumnTypeHelper
|
|
43
43
|
setter_type = getter_type
|
44
44
|
|
45
45
|
if column&.null
|
46
|
-
return [
|
46
|
+
return [as_nilable_type(getter_type), as_nilable_type(setter_type)]
|
47
47
|
end
|
48
48
|
|
49
49
|
if column_name == @constant.primary_key ||
|
50
50
|
column_name == "created_at" ||
|
51
51
|
column_name == "updated_at"
|
52
|
-
getter_type =
|
52
|
+
getter_type = as_nilable_type(getter_type)
|
53
53
|
end
|
54
54
|
|
55
55
|
[getter_type, setter_type]
|
@@ -63,9 +63,19 @@ class ActiveRecordColumnTypeHelper
|
|
63
63
|
!(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
|
64
64
|
end
|
65
65
|
|
66
|
+
sig { params(type: String).returns(String) }
|
67
|
+
def as_nilable_type(type)
|
68
|
+
if type.start_with?("T.nilable(") || type == "T.untyped"
|
69
|
+
type
|
70
|
+
else
|
71
|
+
"T.nilable(#{type})"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
66
75
|
sig { params(column_type: Object).returns(String) }
|
67
76
|
def handle_unknown_type(column_type)
|
68
77
|
return "T.untyped" unless ActiveModel::Type::Value === column_type
|
78
|
+
return "T.untyped" if Tapioca::GenericTypeRegistry.generic_type_instance?(column_type)
|
69
79
|
|
70
80
|
lookup_return_type_of_method(column_type, :deserialize) ||
|
71
81
|
lookup_return_type_of_method(column_type, :cast) ||
|
@@ -12,15 +12,16 @@ module Tapioca
|
|
12
12
|
|
13
13
|
sig { params(message: String, color: T.any(Symbol, T::Array[Symbol])).void }
|
14
14
|
def say_error(message = "", *color)
|
15
|
-
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
# Thor has its own `say_error` now, but it has two problems:
|
16
|
+
# 1. it adds the padding around all the messages, even if they continue on
|
17
|
+
# the same line, and
|
18
|
+
# 2. it accepts a last parameter which breaks the ability to pass color values
|
19
|
+
# as splats.
|
20
|
+
#
|
21
|
+
# So we implement our own version here to work around those problems.
|
22
|
+
shell.indent(-shell.padding) do
|
23
|
+
super(message, color)
|
24
|
+
end
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
@@ -6,6 +6,9 @@ require "yaml"
|
|
6
6
|
module Tapioca
|
7
7
|
module ConfigHelper
|
8
8
|
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
requires_ancestor { Thor }
|
9
12
|
|
10
13
|
sig { returns(String) }
|
11
14
|
attr_reader :command_name
|
@@ -60,9 +63,122 @@ module Tapioca
|
|
60
63
|
config = YAML.load_file(config_file, fallback: {})
|
61
64
|
end
|
62
65
|
|
66
|
+
validate_config!(config_file, config)
|
67
|
+
|
63
68
|
Thor::CoreExt::HashWithIndifferentAccess.new(config[command_name] || {})
|
64
69
|
end
|
65
70
|
|
71
|
+
sig { params(config_file: String, config: T::Hash[T.untyped, T.untyped]).void }
|
72
|
+
def validate_config!(config_file, config)
|
73
|
+
# To ensure that this is not re-entered, we mark during validation
|
74
|
+
return if @validating_config
|
75
|
+
@validating_config = T.let(true, T.nilable(T::Boolean))
|
76
|
+
|
77
|
+
commands = T.cast(self, Thor).class.commands
|
78
|
+
|
79
|
+
errors = config.flat_map do |config_key, config_options|
|
80
|
+
command = commands[config_key.to_s]
|
81
|
+
|
82
|
+
unless command
|
83
|
+
next build_error("unknown key `#{config_key}`")
|
84
|
+
end
|
85
|
+
|
86
|
+
validate_config_options(command.options, config_key, config_options || {})
|
87
|
+
end.compact
|
88
|
+
|
89
|
+
unless errors.empty?
|
90
|
+
print_errors(config_file, errors)
|
91
|
+
exit(1)
|
92
|
+
end
|
93
|
+
ensure
|
94
|
+
@validating_config = false
|
95
|
+
end
|
96
|
+
|
97
|
+
sig do
|
98
|
+
params(
|
99
|
+
command_options: T::Hash[Symbol, Thor::Option],
|
100
|
+
config_key: String,
|
101
|
+
config_options: T::Hash[T.untyped, T.untyped]
|
102
|
+
).returns(T::Array[ConfigError])
|
103
|
+
end
|
104
|
+
def validate_config_options(command_options, config_key, config_options)
|
105
|
+
config_options.map do |config_option_key, config_option_value|
|
106
|
+
command_option = command_options[config_option_key.to_sym]
|
107
|
+
|
108
|
+
unless command_option
|
109
|
+
next build_error("unknown option `#{config_option_key}` for key `#{config_key}`")
|
110
|
+
end
|
111
|
+
|
112
|
+
config_option_value_type = case config_option_value
|
113
|
+
when FalseClass, TrueClass
|
114
|
+
:boolean
|
115
|
+
when Numeric
|
116
|
+
:numeric
|
117
|
+
when Hash
|
118
|
+
:hash
|
119
|
+
when Array
|
120
|
+
:array
|
121
|
+
when String
|
122
|
+
:string
|
123
|
+
else
|
124
|
+
:object
|
125
|
+
end
|
126
|
+
|
127
|
+
unless config_option_value_type == command_option.type
|
128
|
+
next build_error("invalid value for option `#{config_option_key}` for key `#{config_key}` " \
|
129
|
+
"- expected `#{command_option.type.capitalize}` but found #{config_option_value_type.capitalize}")
|
130
|
+
end
|
131
|
+
end.compact
|
132
|
+
end
|
133
|
+
|
134
|
+
class ConfigErrorMessagePart < T::Struct
|
135
|
+
const :message, String
|
136
|
+
const :colors, T::Array[Symbol]
|
137
|
+
end
|
138
|
+
|
139
|
+
class ConfigError < T::Struct
|
140
|
+
const :message_parts, T::Array[ConfigErrorMessagePart]
|
141
|
+
end
|
142
|
+
|
143
|
+
sig { params(msg: String).returns(ConfigError) }
|
144
|
+
def build_error(msg)
|
145
|
+
parts = msg.split(/(`[^`]+` ?)/)
|
146
|
+
|
147
|
+
message_parts = parts.map do |part|
|
148
|
+
match = part.match(/`([^`]+)`( ?)/)
|
149
|
+
|
150
|
+
if match
|
151
|
+
ConfigErrorMessagePart.new(
|
152
|
+
message: "#{match[1]}#{match[2]}",
|
153
|
+
colors: [:bold, :blue]
|
154
|
+
)
|
155
|
+
else
|
156
|
+
ConfigErrorMessagePart.new(
|
157
|
+
message: part,
|
158
|
+
colors: [:yellow]
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
ConfigError.new(
|
164
|
+
message_parts: message_parts
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
sig { params(config_file: String, errors: T::Array[ConfigError]).void }
|
169
|
+
def print_errors(config_file, errors)
|
170
|
+
say_error("\nConfiguration file ", :red)
|
171
|
+
say_error("#{config_file} ", :blue, :bold)
|
172
|
+
say_error("has the following errors:\n\n", :red)
|
173
|
+
|
174
|
+
errors.each do |error|
|
175
|
+
say_error("- ")
|
176
|
+
error.message_parts.each do |part|
|
177
|
+
T.unsafe(self).say_error(part.message, *part.colors)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
66
182
|
sig do
|
67
183
|
params(options: T.nilable(Thor::CoreExt::HashWithIndifferentAccess))
|
68
184
|
.returns(Thor::CoreExt::HashWithIndifferentAccess)
|
@@ -23,17 +23,31 @@ module T
|
|
23
23
|
def type_member(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
|
24
24
|
# `T::Generic#type_member` just instantiates a `T::Type::TypeMember` instance and returns it.
|
25
25
|
# We use that when registering the type member and then later return it from this method.
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
Tapioca::TypeVariableModule.new(
|
27
|
+
T.cast(self, Module),
|
28
|
+
Tapioca::TypeVariableModule::Type::Member,
|
29
|
+
variance,
|
30
|
+
fixed,
|
31
|
+
lower,
|
32
|
+
upper
|
33
|
+
).tap do |type_variable|
|
34
|
+
Tapioca::GenericTypeRegistry.register_type_variable(self, type_variable)
|
35
|
+
end
|
29
36
|
end
|
30
37
|
|
31
38
|
def type_template(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
|
32
39
|
# `T::Generic#type_template` just instantiates a `T::Type::TypeTemplate` instance and returns it.
|
33
40
|
# We use that when registering the type template and then later return it from this method.
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
Tapioca::TypeVariableModule.new(
|
42
|
+
T.cast(self, Module),
|
43
|
+
Tapioca::TypeVariableModule::Type::Template,
|
44
|
+
variance,
|
45
|
+
fixed,
|
46
|
+
lower,
|
47
|
+
upper
|
48
|
+
).tap do |type_variable|
|
49
|
+
Tapioca::GenericTypeRegistry.register_type_variable(self, type_variable)
|
50
|
+
end
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
@@ -42,15 +56,26 @@ module T
|
|
42
56
|
|
43
57
|
module Types
|
44
58
|
class Simple
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
59
|
+
module GenericPatch
|
60
|
+
def valid?(obj)
|
61
|
+
# Since `Tapioca::TypeVariable` is a `Module`, it will be wrapped by a
|
62
|
+
# `Simple` type. We want to always make type variable types valid, so we
|
63
|
+
# need to explicitly check that `raw_type` is a `Tapioca::TypeVariable`
|
64
|
+
# and return `true`
|
65
|
+
if defined?(Tapioca::TypeVariableModule) && Tapioca::TypeVariableModule === @raw_type
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
|
69
|
+
obj.is_a?(@raw_type)
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method intercepts calls to the `name` method for simple types, so that
|
73
|
+
# it can ask the name to the type if the type is generic, since, by this point,
|
74
|
+
# we've created a clone of that type with the `name` method returning the
|
75
|
+
# appropriate name for that specific concrete type.
|
51
76
|
def name
|
52
|
-
if T::Generic === @raw_type
|
53
|
-
# for types that are generic, use the name
|
77
|
+
if T::Generic === @raw_type || Tapioca::TypeVariableModule === @raw_type
|
78
|
+
# for types that are generic or are type variables, use the name
|
54
79
|
# returned by the "name" method of this instance
|
55
80
|
@name ||= T.unsafe(@raw_type).name.freeze
|
56
81
|
else
|
@@ -60,60 +85,57 @@ module T
|
|
60
85
|
end
|
61
86
|
end
|
62
87
|
|
63
|
-
prepend
|
88
|
+
prepend GenericPatch
|
64
89
|
end
|
65
90
|
end
|
66
91
|
end
|
67
92
|
|
68
93
|
module Tapioca
|
69
|
-
|
94
|
+
# This is subclassing from `Module` so that instances of this type will be modules.
|
95
|
+
# The reason why we want that is because that means those instances will automatically
|
96
|
+
# get bound to the constant names they are assigned to by Ruby. As a result, we don't
|
97
|
+
# need to do any matching of constants to type variables to bind their names, Ruby will
|
98
|
+
# do that automatically for us and we get the `name` method for free from `Module`.
|
99
|
+
class TypeVariableModule < Module
|
70
100
|
extend T::Sig
|
71
101
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
102
|
+
class Type < T::Enum
|
103
|
+
enums do
|
104
|
+
Member = new("type_member")
|
105
|
+
Template = new("type_template")
|
106
|
+
end
|
107
|
+
end
|
77
108
|
|
78
|
-
sig
|
79
|
-
|
80
|
-
|
109
|
+
sig do
|
110
|
+
params(context: Module, type: Type, variance: Symbol, fixed: T.untyped, lower: T.untyped, upper: T.untyped).void
|
111
|
+
end
|
112
|
+
def initialize(context, type, variance, fixed, lower, upper) # rubocop:disable Metrics/ParameterLists
|
113
|
+
@context = context
|
114
|
+
@type = type
|
115
|
+
@variance = variance
|
81
116
|
@fixed = fixed
|
82
117
|
@lower = lower
|
83
118
|
@upper = upper
|
119
|
+
super()
|
84
120
|
end
|
85
121
|
|
86
|
-
sig { returns(String) }
|
87
|
-
def serialize
|
88
|
-
parts = []
|
89
|
-
parts << ":#{@variance}" unless @variance == :invariant
|
90
|
-
parts << "fixed: #{@fixed}" if @fixed
|
91
|
-
parts << "lower: #{@lower}" unless @lower == T.untyped
|
92
|
-
parts << "upper: #{@upper}" unless @upper == BasicObject
|
93
|
-
|
94
|
-
parameters = parts.join(", ")
|
95
|
-
|
96
|
-
serialized = +"type_member"
|
97
|
-
serialized << "(#{parameters})" unless parameters.empty?
|
98
|
-
serialized
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
class TypeTemplate < T::Types::TypeTemplate
|
103
|
-
extend T::Sig
|
104
|
-
|
105
122
|
sig { returns(T.nilable(String)) }
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
123
|
+
def name
|
124
|
+
constant_name = super
|
125
|
+
|
126
|
+
# This is a hack to work around modules under anonymous modules not having
|
127
|
+
# names in 2.6 and 2.7: https://bugs.ruby-lang.org/issues/14895
|
128
|
+
#
|
129
|
+
# This happens when a type variable is declared under `class << self`, for
|
130
|
+
# example.
|
131
|
+
#
|
132
|
+
# The workaround is to give the parent context a name, at which point, our
|
133
|
+
# module gets bound to a name under that name, as well.
|
134
|
+
unless constant_name
|
135
|
+
constant_name = with_bound_name_pre_3_0 { super }
|
136
|
+
end
|
110
137
|
|
111
|
-
|
112
|
-
def initialize(variance, fixed, lower, upper)
|
113
|
-
super(variance)
|
114
|
-
@fixed = fixed
|
115
|
-
@lower = lower
|
116
|
-
@upper = upper
|
138
|
+
constant_name&.split("::")&.last
|
117
139
|
end
|
118
140
|
|
119
141
|
sig { returns(String) }
|
@@ -126,9 +148,25 @@ module Tapioca
|
|
126
148
|
|
127
149
|
parameters = parts.join(", ")
|
128
150
|
|
129
|
-
serialized =
|
151
|
+
serialized = @type.serialize.dup
|
130
152
|
serialized << "(#{parameters})" unless parameters.empty?
|
131
153
|
serialized
|
132
154
|
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
sig do
|
159
|
+
type_parameters(:Result)
|
160
|
+
.params(block: T.proc.returns(T.type_parameter(:Result)))
|
161
|
+
.returns(T.type_parameter(:Result))
|
162
|
+
end
|
163
|
+
def with_bound_name_pre_3_0(&block)
|
164
|
+
require "securerandom"
|
165
|
+
temp_name = "TYPE_VARIABLE_TRACKING_#{SecureRandom.hex}"
|
166
|
+
self.class.const_set(temp_name, @context)
|
167
|
+
block.call
|
168
|
+
ensure
|
169
|
+
self.class.send(:remove_const, temp_name) if temp_name
|
170
|
+
end
|
133
171
|
end
|
134
172
|
end
|
data/lib/tapioca/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapioca
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ufuk Kayserilioglu
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2022-01-20 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|