rigortype 0.0.2 → 0.0.4
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 +24 -7
- data/data/builtins/ruby_core/array.yml +1470 -0
- data/data/builtins/ruby_core/file.yml +501 -0
- data/data/builtins/ruby_core/hash.yml +936 -0
- data/data/builtins/ruby_core/io.yml +1594 -0
- data/data/builtins/ruby_core/numeric.yml +1809 -0
- data/data/builtins/ruby_core/range.yml +389 -0
- data/data/builtins/ruby_core/set.yml +594 -0
- data/data/builtins/ruby_core/string.yml +1850 -0
- data/data/builtins/ruby_core/time.yml +750 -0
- data/lib/rigor/analysis/check_rules.rb +97 -4
- data/lib/rigor/analysis/runner.rb +4 -0
- data/lib/rigor/builtins/imported_refinements.rb +251 -0
- data/lib/rigor/configuration.rb +6 -1
- data/lib/rigor/inference/acceptance.rb +324 -6
- data/lib/rigor/inference/builtins/array_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/hash_catalog.rb +40 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +90 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +93 -0
- data/lib/rigor/inference/builtins/range_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/set_catalog.rb +54 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +39 -0
- data/lib/rigor/inference/builtins/time_catalog.rb +64 -0
- data/lib/rigor/inference/expression_typer.rb +48 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +670 -16
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +144 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +215 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -7
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +240 -4
- data/lib/rigor/inference/method_dispatcher.rb +28 -21
- data/lib/rigor/inference/method_parameter_binder.rb +29 -4
- data/lib/rigor/inference/narrowing.rb +376 -4
- data/lib/rigor/inference/scope_indexer.rb +10 -2
- data/lib/rigor/inference/statement_evaluator.rb +213 -2
- data/lib/rigor/rbs_extended.rb +230 -15
- data/lib/rigor/scope.rb +14 -0
- data/lib/rigor/type/combinator.rb +159 -1
- data/lib/rigor/type/difference.rb +155 -0
- data/lib/rigor/type/integer_range.rb +137 -0
- data/lib/rigor/type/intersection.rb +135 -0
- data/lib/rigor/type/refined.rb +174 -0
- data/lib/rigor/type.rb +4 -0
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/rbs_extended.rbs +14 -0
- data/sig/rigor/scope.rbs +1 -0
- data/sig/rigor/type.rbs +91 -1
- metadata +25 -1
|
@@ -88,14 +88,51 @@ module Rigor
|
|
|
88
88
|
|
|
89
89
|
# @return [Rigor::Type, nil] the precise element/value type, or
|
|
90
90
|
# `nil` to defer to the next dispatcher tier.
|
|
91
|
+
# Per-carrier dispatch table. Adding a new carrier here
|
|
92
|
+
# is a one-row change; the helper methods stay private.
|
|
93
|
+
# Anonymous Type subclasses are not expected.
|
|
94
|
+
RECEIVER_HANDLERS = {
|
|
95
|
+
Type::Tuple => :dispatch_tuple,
|
|
96
|
+
Type::HashShape => :dispatch_hash_shape,
|
|
97
|
+
Type::Nominal => :dispatch_nominal_size,
|
|
98
|
+
Type::Difference => :dispatch_difference,
|
|
99
|
+
Type::Refined => :dispatch_refined,
|
|
100
|
+
Type::Intersection => :dispatch_intersection
|
|
101
|
+
}.freeze
|
|
102
|
+
private_constant :RECEIVER_HANDLERS
|
|
103
|
+
|
|
91
104
|
def try_dispatch(receiver:, method_name:, args:)
|
|
92
105
|
args ||= []
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
handler = RECEIVER_HANDLERS[receiver.class]
|
|
107
|
+
return nil unless handler
|
|
108
|
+
|
|
109
|
+
send(handler, receiver, method_name, args)
|
|
97
110
|
end
|
|
98
111
|
|
|
112
|
+
# Tightens `Array#size` / `Array#length` / `String#length` /
|
|
113
|
+
# `String#bytesize` / `Hash#size` etc. on a `Nominal` receiver
|
|
114
|
+
# from the RBS-declared `Integer` to `non_negative_int`. The
|
|
115
|
+
# tier ahead of RBS sees the more precise carrier so
|
|
116
|
+
# downstream narrowing (`if size > 0; …`) actually has a
|
|
117
|
+
# range to intersect with.
|
|
118
|
+
SIZE_RETURNING_NOMINALS = {
|
|
119
|
+
"Array" => %i[size length count],
|
|
120
|
+
"String" => %i[length size bytesize],
|
|
121
|
+
"Hash" => %i[size length count],
|
|
122
|
+
"Set" => %i[size length count],
|
|
123
|
+
"Range" => %i[size length count]
|
|
124
|
+
}.freeze
|
|
125
|
+
private_constant :SIZE_RETURNING_NOMINALS
|
|
126
|
+
|
|
127
|
+
# When the difference removes the empty value of the
|
|
128
|
+
# base type (`Constant[""]`, `Constant[0]`, an empty
|
|
129
|
+
# Tuple, an empty HashShape), `size` / `length` /
|
|
130
|
+
# `count` MUST be `positive-int` (the base's
|
|
131
|
+
# non-negative range minus the removed point's `0`),
|
|
132
|
+
# and `empty?` / `zero?` MUST be `Constant[false]`.
|
|
133
|
+
EMPTY_REMOVAL_BASES = %w[String Array Hash Set].freeze
|
|
134
|
+
private_constant :EMPTY_REMOVAL_BASES
|
|
135
|
+
|
|
99
136
|
class << self
|
|
100
137
|
private
|
|
101
138
|
|
|
@@ -113,6 +150,205 @@ module Rigor
|
|
|
113
150
|
send(handler, shape, method_name, args)
|
|
114
151
|
end
|
|
115
152
|
|
|
153
|
+
def dispatch_nominal_size(nominal, method_name, args)
|
|
154
|
+
return nil unless args.empty?
|
|
155
|
+
|
|
156
|
+
selectors = SIZE_RETURNING_NOMINALS[nominal.class_name]
|
|
157
|
+
return nil unless selectors&.include?(method_name)
|
|
158
|
+
|
|
159
|
+
Type::Combinator.non_negative_int
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Refinement-aware projections over a `Difference[base,
|
|
163
|
+
# removed]` receiver. When the removed value is the
|
|
164
|
+
# empty witness of the base (`Constant[""]` for
|
|
165
|
+
# String, `Tuple[]` for Array, `HashShape{}` for Hash,
|
|
166
|
+
# `Constant[0]` for Integer), the catalog tier knows:
|
|
167
|
+
#
|
|
168
|
+
# ns.size # positive-int
|
|
169
|
+
# ns.size == 0 # Constant[false] (via narrowing tier)
|
|
170
|
+
# ns.empty? # Constant[false]
|
|
171
|
+
# nzi.zero? # Constant[false]
|
|
172
|
+
#
|
|
173
|
+
# For any other base method, the difference is opaque
|
|
174
|
+
# to ShapeDispatch — we delegate to the base nominal
|
|
175
|
+
# so the size/length tier still answers the broader
|
|
176
|
+
# `non_negative_int` envelope where applicable.
|
|
177
|
+
def dispatch_difference(difference, method_name, args)
|
|
178
|
+
base = difference.base
|
|
179
|
+
return nil unless base.is_a?(Type::Nominal)
|
|
180
|
+
|
|
181
|
+
if removes_empty_witness?(difference)
|
|
182
|
+
precise = empty_removal_projection(base, method_name, args)
|
|
183
|
+
return precise if precise
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
dispatch_nominal_size(base, method_name, args)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
EMPTY_WITNESS_PREDICATES = {
|
|
190
|
+
"String" => ->(removed) { removed.is_a?(Type::Constant) && removed.value == "" },
|
|
191
|
+
"Integer" => lambda { |removed|
|
|
192
|
+
removed.is_a?(Type::Constant) && removed.value.is_a?(Integer) && removed.value.zero?
|
|
193
|
+
},
|
|
194
|
+
"Array" => ->(removed) { removed.is_a?(Type::Tuple) && removed.elements.empty? },
|
|
195
|
+
"Hash" => ->(removed) { removed.is_a?(Type::HashShape) && removed.pairs.empty? }
|
|
196
|
+
}.freeze
|
|
197
|
+
private_constant :EMPTY_WITNESS_PREDICATES
|
|
198
|
+
|
|
199
|
+
def removes_empty_witness?(difference)
|
|
200
|
+
return false unless difference.base.is_a?(Type::Nominal)
|
|
201
|
+
|
|
202
|
+
predicate = EMPTY_WITNESS_PREDICATES[difference.base.class_name]
|
|
203
|
+
!!(predicate && predicate.call(difference.removed))
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def empty_removal_projection(base, method_name, args)
|
|
207
|
+
return nil unless args.empty?
|
|
208
|
+
|
|
209
|
+
if %i[size length count bytesize].include?(method_name)
|
|
210
|
+
return size_returning_for_empty_removal(base, method_name)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
empty_predicate_projection(base, method_name)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def empty_predicate_projection(base, method_name)
|
|
217
|
+
case method_name
|
|
218
|
+
when :empty?
|
|
219
|
+
base.class_name == "Integer" ? nil : Type::Combinator.constant_of(false)
|
|
220
|
+
when :zero?
|
|
221
|
+
base.class_name == "Integer" ? Type::Combinator.constant_of(false) : nil
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def size_returning_for_empty_removal(base, method_name)
|
|
226
|
+
return nil if base.class_name == "Integer" # Integer has no size method on Difference
|
|
227
|
+
|
|
228
|
+
selectors = SIZE_RETURNING_NOMINALS[base.class_name]
|
|
229
|
+
return nil unless selectors&.include?(method_name)
|
|
230
|
+
|
|
231
|
+
Type::Combinator.positive_int
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Predicate-subset projections over a `Refined[base,
|
|
235
|
+
# predicate]` receiver. Today the catalogue is the
|
|
236
|
+
# String case-normalisation pair: `s.downcase` over a
|
|
237
|
+
# `lowercase-string` receiver folds to the same
|
|
238
|
+
# carrier (already lowercase), and `s.upcase` lifts a
|
|
239
|
+
# `lowercase-string` to `uppercase-string`. Symmetric
|
|
240
|
+
# rules apply with the predicates swapped. Numeric-
|
|
241
|
+
# string idempotence over `#downcase` / `#upcase` is
|
|
242
|
+
# also recognised because a numeric string equals its
|
|
243
|
+
# own case-normalisation.
|
|
244
|
+
#
|
|
245
|
+
# For methods this tier does not have a refinement-
|
|
246
|
+
# specific rule for, projection delegates to
|
|
247
|
+
# `dispatch_nominal_size` so size-returning calls on
|
|
248
|
+
# a `Refined[String, *]` still tighten to
|
|
249
|
+
# `non_negative_int`.
|
|
250
|
+
REFINED_STRING_PROJECTIONS = {
|
|
251
|
+
%i[lowercase downcase] => :refined_self,
|
|
252
|
+
%i[lowercase upcase] => :uppercase_string,
|
|
253
|
+
%i[uppercase upcase] => :refined_self,
|
|
254
|
+
%i[uppercase downcase] => :lowercase_string,
|
|
255
|
+
%i[numeric downcase] => :refined_self,
|
|
256
|
+
%i[numeric upcase] => :refined_self,
|
|
257
|
+
# Digit-only strings are case-invariant; the prefix
|
|
258
|
+
# letters in `0o…` / `0x…` are accepted by the
|
|
259
|
+
# predicate in either case so the predicate-subset
|
|
260
|
+
# is preserved across `#downcase` / `#upcase` even
|
|
261
|
+
# though the value-set element changes.
|
|
262
|
+
%i[decimal_int downcase] => :refined_self,
|
|
263
|
+
%i[decimal_int upcase] => :refined_self,
|
|
264
|
+
%i[octal_int downcase] => :refined_self,
|
|
265
|
+
%i[octal_int upcase] => :refined_self,
|
|
266
|
+
%i[hex_int downcase] => :refined_self,
|
|
267
|
+
%i[hex_int upcase] => :refined_self
|
|
268
|
+
}.freeze
|
|
269
|
+
private_constant :REFINED_STRING_PROJECTIONS
|
|
270
|
+
|
|
271
|
+
def dispatch_refined(refined, method_name, args)
|
|
272
|
+
base = refined.base
|
|
273
|
+
return nil unless base.is_a?(Type::Nominal)
|
|
274
|
+
|
|
275
|
+
if base.class_name == "String" && args.empty?
|
|
276
|
+
precise = refined_string_projection(refined, method_name)
|
|
277
|
+
return precise if precise
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
dispatch_nominal_size(base, method_name, args)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def refined_string_projection(refined, method_name)
|
|
284
|
+
handler = REFINED_STRING_PROJECTIONS[[refined.predicate_id, method_name]]
|
|
285
|
+
return nil unless handler
|
|
286
|
+
|
|
287
|
+
case handler
|
|
288
|
+
when :refined_self then refined
|
|
289
|
+
when :uppercase_string then Type::Combinator.uppercase_string
|
|
290
|
+
when :lowercase_string then Type::Combinator.lowercase_string
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Projects a method call over an `Intersection[M1, …]`
|
|
295
|
+
# receiver by collecting each member's projection and
|
|
296
|
+
# combining the results. The set-theoretic identity is
|
|
297
|
+
# `M(A ∩ B) ⊆ M(A) ∩ M(B)`, so the meet of the per-member
|
|
298
|
+
# projections is sound. Combining is best-effort:
|
|
299
|
+
#
|
|
300
|
+
# - If every result is a `Type::IntegerRange`, return
|
|
301
|
+
# their bounded-integer meet (max of lower bounds, min
|
|
302
|
+
# of upper bounds). This catches the common
|
|
303
|
+
# `(non_empty_string ∩ lowercase_string).size`
|
|
304
|
+
# pattern where one member projects to `positive-int`
|
|
305
|
+
# and the other to `non-negative-int`; the meet is
|
|
306
|
+
# `positive-int`.
|
|
307
|
+
# - Otherwise return the first non-nil result. A richer
|
|
308
|
+
# meet (e.g. of Difference + Refined results when both
|
|
309
|
+
# project) is left for a future slice; the carrier
|
|
310
|
+
# stays sound because every member's projection is
|
|
311
|
+
# already a superset of the true intersection.
|
|
312
|
+
#
|
|
313
|
+
# Returns nil when no member projects, so the caller
|
|
314
|
+
# falls through to the next dispatcher tier.
|
|
315
|
+
def dispatch_intersection(intersection, method_name, args)
|
|
316
|
+
results = intersection.members.filter_map do |member|
|
|
317
|
+
ShapeDispatch.try_dispatch(receiver: member, method_name: method_name, args: args)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
case results.size
|
|
321
|
+
when 0 then nil
|
|
322
|
+
when 1 then results.first
|
|
323
|
+
else combine_intersection_results(results)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def combine_intersection_results(results)
|
|
328
|
+
return narrow_integer_ranges(results) if results.all?(Type::IntegerRange)
|
|
329
|
+
|
|
330
|
+
results.first
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Compute the bounded-integer meet of two or more
|
|
334
|
+
# `IntegerRange` carriers. We compare via the numeric
|
|
335
|
+
# `lower` / `upper` accessors (`-Float::INFINITY` /
|
|
336
|
+
# `Float::INFINITY` for the symbolic ends), then map
|
|
337
|
+
# back to the symbolic-bound representation
|
|
338
|
+
# `IntegerRange.new` expects. The disjoint-meet case
|
|
339
|
+
# cannot arise from sound member-wise projections in
|
|
340
|
+
# v0.0.4 but is guarded defensively to keep the
|
|
341
|
+
# carrier total.
|
|
342
|
+
def narrow_integer_ranges(ranges)
|
|
343
|
+
numeric_low = ranges.map(&:lower).max
|
|
344
|
+
numeric_high = ranges.map(&:upper).min
|
|
345
|
+
return Type::Combinator.bot if numeric_low > numeric_high
|
|
346
|
+
|
|
347
|
+
min = numeric_low == -Float::INFINITY ? Type::IntegerRange::NEG_INFINITY : numeric_low.to_i
|
|
348
|
+
max = numeric_high == Float::INFINITY ? Type::IntegerRange::POS_INFINITY : numeric_high.to_i
|
|
349
|
+
Type::Combinator.integer_range(min, max)
|
|
350
|
+
end
|
|
351
|
+
|
|
116
352
|
def tuple_first(tuple, _method_name, args)
|
|
117
353
|
return nil unless args.empty?
|
|
118
354
|
return Type::Combinator.constant_of(nil) if tuple.elements.empty?
|
|
@@ -4,6 +4,8 @@ require_relative "../type"
|
|
|
4
4
|
require_relative "method_dispatcher/constant_folding"
|
|
5
5
|
require_relative "method_dispatcher/shape_dispatch"
|
|
6
6
|
require_relative "method_dispatcher/rbs_dispatch"
|
|
7
|
+
require_relative "method_dispatcher/iterator_dispatch"
|
|
8
|
+
require_relative "method_dispatcher/file_folding"
|
|
7
9
|
|
|
8
10
|
module Rigor
|
|
9
11
|
module Inference
|
|
@@ -56,29 +58,12 @@ module Rigor
|
|
|
56
58
|
def dispatch(receiver_type:, method_name:, arg_types:, block_type: nil, environment: nil)
|
|
57
59
|
return nil if receiver_type.nil?
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
constant_result = ConstantFolding.try_fold(
|
|
63
|
-
receiver: receiver_type,
|
|
64
|
-
method_name: method_name,
|
|
65
|
-
args: arg_types
|
|
66
|
-
)
|
|
67
|
-
return constant_result if constant_result
|
|
68
|
-
|
|
69
|
-
shape_result = ShapeDispatch.try_dispatch(
|
|
70
|
-
receiver: receiver_type,
|
|
71
|
-
method_name: method_name,
|
|
72
|
-
args: arg_types
|
|
73
|
-
)
|
|
74
|
-
return shape_result if shape_result
|
|
61
|
+
precise = dispatch_precise_tiers(receiver_type, method_name, arg_types)
|
|
62
|
+
return precise if precise
|
|
75
63
|
|
|
76
64
|
rbs_result = RbsDispatch.try_dispatch(
|
|
77
|
-
receiver: receiver_type,
|
|
78
|
-
|
|
79
|
-
args: arg_types,
|
|
80
|
-
environment: environment,
|
|
81
|
-
block_type: block_type
|
|
65
|
+
receiver: receiver_type, method_name: method_name, args: arg_types,
|
|
66
|
+
environment: environment, block_type: block_type
|
|
82
67
|
)
|
|
83
68
|
return rbs_result if rbs_result
|
|
84
69
|
|
|
@@ -96,6 +81,21 @@ module Rigor
|
|
|
96
81
|
try_user_class_fallback(receiver_type, method_name, arg_types, environment, block_type)
|
|
97
82
|
end
|
|
98
83
|
|
|
84
|
+
# Runs the precision tiers (constant fold, shape dispatch,
|
|
85
|
+
# file-path fold) in order and returns the first non-nil
|
|
86
|
+
# answer. Each tier owns its own receiver/argument shape
|
|
87
|
+
# checks; a tier that does not recognise the receiver returns
|
|
88
|
+
# nil so the next tier can try. The RBS tier sits below this
|
|
89
|
+
# chain and is invoked by the outer `dispatch` method.
|
|
90
|
+
def dispatch_precise_tiers(receiver_type, method_name, arg_types)
|
|
91
|
+
meta_result = try_meta_introspection(receiver_type, method_name)
|
|
92
|
+
return meta_result if meta_result
|
|
93
|
+
|
|
94
|
+
ConstantFolding.try_fold(receiver: receiver_type, method_name: method_name, args: arg_types) ||
|
|
95
|
+
ShapeDispatch.try_dispatch(receiver: receiver_type, method_name: method_name, args: arg_types) ||
|
|
96
|
+
FileFolding.try_dispatch(receiver: receiver_type, method_name: method_name, args: arg_types)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
99
|
def try_user_class_fallback(receiver_type, method_name, arg_types, environment, block_type)
|
|
100
100
|
return nil if environment.nil?
|
|
101
101
|
|
|
@@ -201,6 +201,13 @@ module Rigor
|
|
|
201
201
|
def expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil)
|
|
202
202
|
return [] if receiver_type.nil?
|
|
203
203
|
|
|
204
|
+
iterator_result = IteratorDispatch.block_param_types(
|
|
205
|
+
receiver: receiver_type,
|
|
206
|
+
method_name: method_name,
|
|
207
|
+
args: arg_types
|
|
208
|
+
)
|
|
209
|
+
return iterator_result if iterator_result
|
|
210
|
+
|
|
204
211
|
RbsDispatch.block_param_types(
|
|
205
212
|
receiver: receiver_type,
|
|
206
213
|
method_name: method_name,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
5
|
require_relative "../type"
|
|
6
|
+
require_relative "../rbs_extended"
|
|
6
7
|
require_relative "rbs_type_translator"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
@@ -59,10 +60,13 @@ module Rigor
|
|
|
59
60
|
rbs_method = lookup_rbs_method(def_node)
|
|
60
61
|
return types unless rbs_method
|
|
61
62
|
|
|
62
|
-
method_types
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
apply_rbs_overloads(types, slots, rbs_method.method_types) unless rbs_method.method_types.empty?
|
|
64
|
+
# `rigor:v1:param: <name> <refinement>` annotations
|
|
65
|
+
# tighten the bound type for matching slots. Applied
|
|
66
|
+
# after the RBS-overload pass so the override is the
|
|
67
|
+
# authoritative answer regardless of what the RBS
|
|
68
|
+
# signature declared.
|
|
69
|
+
apply_param_overrides(types, slots, rbs_method)
|
|
66
70
|
types
|
|
67
71
|
end
|
|
68
72
|
|
|
@@ -165,6 +169,27 @@ module Rigor
|
|
|
165
169
|
end
|
|
166
170
|
end
|
|
167
171
|
|
|
172
|
+
# Reads the override map off the method's annotations and
|
|
173
|
+
# replaces the binding for any slot whose name appears in
|
|
174
|
+
# the map. Anonymous slots are skipped (no name to match).
|
|
175
|
+
# The override is used verbatim — no `:rest_*` re-wrapping —
|
|
176
|
+
# so authors who tighten a `*rest` parameter to e.g.
|
|
177
|
+
# `non-empty-array[Integer]` describe the parameter binding
|
|
178
|
+
# they actually want, not its element type.
|
|
179
|
+
def apply_param_overrides(types, slots, rbs_method)
|
|
180
|
+
override_map = RbsExtended.param_type_override_map(rbs_method)
|
|
181
|
+
return if override_map.empty?
|
|
182
|
+
|
|
183
|
+
slots.each do |slot|
|
|
184
|
+
next if slot.name.nil?
|
|
185
|
+
|
|
186
|
+
override = override_map[slot.name]
|
|
187
|
+
next if override.nil?
|
|
188
|
+
|
|
189
|
+
types[slot.name] = override
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
168
193
|
def collect_translated_types(method_types, slot)
|
|
169
194
|
rbs_types = method_types.flat_map do |mt|
|
|
170
195
|
t = rbs_type_for_slot(mt.type, slot)
|