rigortype 0.0.5 → 0.0.6

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.
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # Block-shaped fold dispatch (v0.0.6 phase 1).
9
+ #
10
+ # Sits ahead of `RbsDispatch.try_dispatch` and folds a small
11
+ # set of block-taking Enumerable methods when the inferred
12
+ # block return type is a Ruby-truthy or Ruby-falsey
13
+ # `Type::Constant`. The block-parameter typing for the same
14
+ # methods continues to be answered by `IteratorDispatch`
15
+ # (this module concerns the *return* of the call, not the
16
+ # block-param binding).
17
+ #
18
+ # The methods covered fall in two families:
19
+ #
20
+ # - **Filter-shaped** (`select` / `filter` / `reject` /
21
+ # `take_while` / `drop_while`): the block's truthiness
22
+ # selects the all-or-nothing endpoints — either the
23
+ # receiver's full shape (when every element is kept) or
24
+ # the empty-tuple carrier (when every element is dropped).
25
+ # - **Predicate-shaped** (`all?` / `any?` / `none?`): the
26
+ # block's truthiness combined with the receiver's
27
+ # emptiness collapses the call to a `Constant[bool]` in
28
+ # the cases where Ruby's actual semantics make it
29
+ # unconditional. Non-empty + truthy `any?` is `true`;
30
+ # non-empty + falsey `all?` is `false`; the empty-receiver
31
+ # "vacuous" answers (`[].all? { false } == true`,
32
+ # `[].any? { true } == false`, `[].none? { true } == true`)
33
+ # are likewise honoured.
34
+ #
35
+ # The dispatcher returns `nil` for any case that cannot be
36
+ # decided from the (receiver-shape, method, block-truthiness)
37
+ # tuple — element-wise block re-evaluation against
38
+ # `Constant<Array>` receivers (the `map` / `filter_map` /
39
+ # `flat_map` precision tier) is reserved for a later slice.
40
+ module BlockFolding # rubocop:disable Metrics/ModuleLength
41
+ module_function
42
+
43
+ FILTER_KEEP_ON_TRUTHY = Set[:select, :filter, :take_while].freeze
44
+ FILTER_KEEP_ON_FALSEY = Set[:reject, :drop_while].freeze
45
+
46
+ PREDICATE_METHODS = Set[:all?, :any?, :none?].freeze
47
+
48
+ # Methods whose answer is `nil` when the block always
49
+ # returns Ruby-falsey — `find` / `detect` short-circuit
50
+ # to nil when nothing matches, `find_index` / `index`
51
+ # likewise. These methods only fold on the falsey side
52
+ # for now; the truthy-block side requires per-position
53
+ # analysis (the index of the first kept element, or the
54
+ # element itself, depend on the receiver's shape and on
55
+ # which positions actually evaluate to truthy).
56
+ FALSEY_BLOCK_NIL_METHODS = Set[:find, :detect, :find_index, :index].freeze
57
+
58
+ # Block-taking `count` returns the number of elements
59
+ # for which the block is truthy. With a Constant-falsey
60
+ # block the answer is unconditionally `Constant[0]`;
61
+ # with a Constant-truthy block on a finitely-sized
62
+ # receiver it is `Constant[size]`.
63
+ COUNT_METHOD = :count
64
+
65
+ # @param receiver [Rigor::Type, nil]
66
+ # @param method_name [Symbol]
67
+ # @param args [Array<Rigor::Type>]
68
+ # @param block_type [Rigor::Type, nil] inferred return type of
69
+ # the call's block. `nil` means "no block at the call site"
70
+ # and disqualifies every rule here.
71
+ # @return [Rigor::Type, nil]
72
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
73
+ def try_fold(receiver:, method_name:, args:, block_type:)
74
+ return nil if receiver.nil? || block_type.nil?
75
+
76
+ truthiness = constant_truthiness(block_type)
77
+ return nil if truthiness.nil?
78
+
79
+ if PREDICATE_METHODS.include?(method_name)
80
+ fold_predicate(receiver, method_name, truthiness)
81
+ elsif filter_method?(method_name)
82
+ fold_filter(receiver, method_name, truthiness)
83
+ elsif FALSEY_BLOCK_NIL_METHODS.include?(method_name)
84
+ fold_falsey_nil_short_circuit(method_name, truthiness, args)
85
+ elsif method_name == COUNT_METHOD
86
+ fold_count(receiver, truthiness, args)
87
+ end
88
+ end
89
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
90
+
91
+ def filter_method?(method_name)
92
+ FILTER_KEEP_ON_TRUTHY.include?(method_name) ||
93
+ FILTER_KEEP_ON_FALSEY.include?(method_name)
94
+ end
95
+
96
+ # Maps the block return type to `:truthy`, `:falsey`, or
97
+ # `nil` (inconclusive). Only `Type::Constant` answers
98
+ # decisively — `Union[true, false]`, `Nominal[…]`, or
99
+ # `Dynamic[T]` keep the dispatcher silent so the RBS
100
+ # tier still owns the call.
101
+ def constant_truthiness(block_type)
102
+ return nil unless block_type.is_a?(Type::Constant)
103
+
104
+ block_type.value ? :truthy : :falsey
105
+ end
106
+
107
+ # Filter-shaped methods collapse to either the receiver
108
+ # (every element kept) or the empty tuple (every element
109
+ # dropped). Tuple-shaped receivers widen to
110
+ # `Array[union of elements]` on the all-kept side because
111
+ # we cannot prove WHICH positional subset survives —
112
+ # Tuple's per-position semantics do not carry over to a
113
+ # filtered Array.
114
+ def fold_filter(receiver, method_name, truthiness)
115
+ return nil unless filter_receiver_known?(receiver)
116
+
117
+ keep_all = filter_keeps_all?(method_name, truthiness)
118
+ keep_all ? receiver_as_kept_array(receiver) : Type::Combinator.tuple_of
119
+ end
120
+
121
+ def filter_keeps_all?(method_name, truthiness)
122
+ (FILTER_KEEP_ON_TRUTHY.include?(method_name) && truthiness == :truthy) ||
123
+ (FILTER_KEEP_ON_FALSEY.include?(method_name) && truthiness == :falsey)
124
+ end
125
+
126
+ def receiver_as_kept_array(receiver)
127
+ case receiver
128
+ when Type::Tuple then tuple_to_array(receiver)
129
+ else receiver
130
+ end
131
+ end
132
+
133
+ def tuple_to_array(tuple)
134
+ return Type::Combinator.tuple_of if tuple.elements.empty?
135
+ return Type::Combinator.nominal_of("Array", type_args: [tuple.elements.first]) if tuple.elements.size == 1
136
+
137
+ element = Type::Combinator.union(*tuple.elements)
138
+ Type::Combinator.nominal_of("Array", type_args: [element])
139
+ end
140
+
141
+ # Predicate folds. The decision table mirrors Ruby's
142
+ # actual semantics on `Enumerable#all?` / `#any?` /
143
+ # `#none?` — see the table at the top of the module.
144
+ def fold_predicate(receiver, method_name, truthiness)
145
+ emptiness = receiver_emptiness(receiver)
146
+ decision = predicate_decision(method_name, truthiness, emptiness)
147
+ return nil if decision.nil?
148
+
149
+ case decision
150
+ when :always_true then Type::Combinator.constant_of(true)
151
+ when :always_false then Type::Combinator.constant_of(false)
152
+ when :bool then bool_union
153
+ end
154
+ end
155
+
156
+ # @return [:always_true, :always_false, :bool, nil]
157
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
158
+ def predicate_decision(method_name, truthiness, emptiness)
159
+ case method_name
160
+ when :all?
161
+ return :always_true if truthiness == :truthy
162
+ return :always_true if emptiness == :empty
163
+ return :always_false if emptiness == :non_empty
164
+
165
+ :bool
166
+ when :any?
167
+ return :always_false if truthiness == :falsey
168
+ return :always_true if emptiness == :non_empty
169
+ return :always_false if emptiness == :empty
170
+
171
+ :bool
172
+ when :none?
173
+ return :always_true if truthiness == :falsey
174
+ return :always_false if emptiness == :non_empty
175
+ return :always_true if emptiness == :empty
176
+
177
+ :bool
178
+ end
179
+ end
180
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
181
+
182
+ def bool_union
183
+ Type::Combinator.union(
184
+ Type::Combinator.constant_of(true),
185
+ Type::Combinator.constant_of(false)
186
+ )
187
+ end
188
+
189
+ # @return [:empty, :non_empty, :unknown]
190
+ def receiver_emptiness(receiver)
191
+ case receiver
192
+ when Type::Tuple
193
+ receiver.elements.empty? ? :empty : :non_empty
194
+ when Type::HashShape
195
+ receiver.pairs.empty? ? :empty : :non_empty
196
+ when Type::Constant
197
+ constant_emptiness(receiver.value)
198
+ when Type::Difference
199
+ difference_emptiness(receiver)
200
+ else
201
+ :unknown
202
+ end
203
+ end
204
+
205
+ def constant_emptiness(value)
206
+ # Only `Range` constants reach these folds: `Type::Constant`
207
+ # rejects Array / Hash literals (they become `Tuple` /
208
+ # `HashShape` carriers), and the remaining scalar
209
+ # constants (Integer / Float / Symbol / String / …)
210
+ # are not Enumerable receivers for the filter or
211
+ # predicate methods folded here.
212
+ return range_emptiness(value) if value.is_a?(Range)
213
+
214
+ :unknown
215
+ end
216
+
217
+ def range_emptiness(range)
218
+ beg = range.begin
219
+ en = range.end
220
+ return :unknown unless beg.is_a?(Numeric) && en.is_a?(Numeric)
221
+
222
+ if range.exclude_end?
223
+ beg < en ? :non_empty : :empty
224
+ else
225
+ beg <= en ? :non_empty : :empty
226
+ end
227
+ end
228
+
229
+ # `non-empty-array[T]` is encoded as
230
+ # `Difference[Array[T], Tuple[]]` — the imported built-in
231
+ # carrier for non-emptiness. Recognising it here lets
232
+ # `arr.any? { true }` fold to `Constant[true]` for
233
+ # callers who threaded the non-emptiness through their
234
+ # type signature.
235
+ def difference_emptiness(diff)
236
+ base = diff.base
237
+ removed = diff.removed
238
+ return :unknown unless removed.is_a?(Type::Tuple) && removed.elements.empty?
239
+ return :non_empty if array_or_hash_nominal?(base)
240
+
241
+ :unknown
242
+ end
243
+
244
+ def array_or_hash_nominal?(type)
245
+ type.is_a?(Type::Nominal) && %w[Array Hash Set].include?(type.class_name)
246
+ end
247
+
248
+ # Filter folds need at least a recognised collection
249
+ # carrier; `Top` / `Dynamic` / arbitrary nominals decline
250
+ # so the RBS tier answers (its `Array#select { … } -> Array[T]`
251
+ # projection is correct, just less precise on the empty
252
+ # endpoint).
253
+ def filter_receiver_known?(receiver)
254
+ case receiver
255
+ when Type::Tuple, Type::HashShape, Type::Constant, Type::Difference then true
256
+ when Type::Nominal then %w[Array Hash Set Range].include?(receiver.class_name)
257
+ else false
258
+ end
259
+ end
260
+
261
+ # `find` / `detect` / `find_index` / `index` (block form)
262
+ # short-circuit to nil when the block is provably falsey.
263
+ # `index` and `find_index` also accept a non-block argument
264
+ # form (`arr.index(value)`); we decline whenever the call
265
+ # carries a positional argument so the RBS tier still
266
+ # answers the value-search variant correctly.
267
+ def fold_falsey_nil_short_circuit(_method_name, truthiness, args)
268
+ return nil unless args.empty?
269
+ return nil unless truthiness == :falsey
270
+
271
+ Type::Combinator.constant_of(nil)
272
+ end
273
+
274
+ # `count` with a block returns the count of elements
275
+ # for which the block is truthy. The non-block forms
276
+ # (`count` / `count(value)`) carry positional arguments
277
+ # and are handled by the RBS tier; this fold only fires
278
+ # when the block is the sole source of selection.
279
+ def fold_count(receiver, truthiness, args)
280
+ return nil unless args.empty?
281
+ return Type::Combinator.constant_of(0) if truthiness == :falsey
282
+
283
+ fold_count_truthy(receiver)
284
+ end
285
+
286
+ def fold_count_truthy(receiver)
287
+ size = finite_size(receiver)
288
+ return nil if size.nil?
289
+
290
+ Type::Combinator.constant_of(size)
291
+ end
292
+
293
+ # Returns the receiver's known finite element count, or
294
+ # nil when the carrier does not pin a size. Tuple and
295
+ # HashShape are pinned by construction; `Constant<…>`
296
+ # exposes the literal's `.size`. Other shapes (Array[T],
297
+ # Range[T], Nominal) decline so the RBS tier widens.
298
+ def finite_size(receiver)
299
+ case receiver
300
+ when Type::Tuple then receiver.elements.size
301
+ when Type::HashShape then receiver.pairs.size
302
+ when Type::Constant then constant_size(receiver.value)
303
+ end
304
+ end
305
+
306
+ def constant_size(value)
307
+ # Mirrors `constant_emptiness` — only `Range` produces
308
+ # a meaningful finite size for the methods folded here.
309
+ range_size(value) if value.is_a?(Range)
310
+ end
311
+
312
+ def range_size(range)
313
+ beg = range.begin
314
+ en = range.end
315
+ return nil unless beg.is_a?(Integer) && en.is_a?(Integer)
316
+
317
+ range.exclude_end? ? [en - beg, 0].max : [en - beg + 1, 0].max
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
@@ -13,6 +13,7 @@ require_relative "../builtins/comparable_catalog"
13
13
  require_relative "../builtins/enumerable_catalog"
14
14
  require_relative "../builtins/rational_catalog"
15
15
  require_relative "../builtins/complex_catalog"
16
+ require_relative "../builtins/pathname_catalog"
16
17
 
17
18
  module Rigor
18
19
  module Inference
@@ -233,18 +234,87 @@ module Rigor
233
234
  # 2-arg fold dispatch. Used by `Comparable#between?(min, max)`,
234
235
  # `Comparable#clamp(min, max)`, and `Integer#pow(exp, mod)` —
235
236
  # methods the catalog classifies `:leaf` but that the prior
236
- # 0/1-arg switch could not reach. Range receivers/args are
237
- # held back: a precise 2-arg range fold (e.g.
238
- # `int<0,10>.between?(0, 10)` `Constant[true]`) is a
239
- # follow-up; for now any IntegerRange operand bails to the
240
- # RBS tier.
237
+ # 0/1-arg switch could not reach.
238
+ #
239
+ # v0.0.6 IntegerRange-shaped receivers participate in
240
+ # `Comparable#between?` and `Comparable#clamp` folds.
241
+ # `int<a,b>.between?(min, max)` decides three-valued via
242
+ # the receiver's bounds against scalar args; `int<a,b>.clamp`
243
+ # narrows the receiver's bounds against the bracket. Other
244
+ # ternary methods over IntegerRange operands still decline.
241
245
  def try_fold_ternary(receiver_set, method_name, arg_sets)
242
- return nil if receiver_set.is_a?(Type::IntegerRange)
246
+ return try_fold_ternary_range(receiver_set, method_name, arg_sets) if receiver_set.is_a?(Type::IntegerRange)
243
247
  return nil if arg_sets.any?(Type::IntegerRange)
244
248
 
245
249
  try_fold_ternary_set(receiver_set, method_name, arg_sets)
246
250
  end
247
251
 
252
+ # Receiver IntegerRange + two scalar `Constant<Integer>`
253
+ # args — the only IntegerRange-aware ternary fold today.
254
+ # `between?` returns Trinary truthiness over the bracket;
255
+ # `clamp` returns the intersected IntegerRange (or a
256
+ # collapsed Constant if the result pins a single point).
257
+ def try_fold_ternary_range(range, method_name, arg_sets)
258
+ return nil unless arg_sets.all?(Array)
259
+
260
+ min_arg = single_integer_arg(arg_sets[0])
261
+ max_arg = single_integer_arg(arg_sets[1])
262
+ return nil if min_arg.nil? || max_arg.nil?
263
+ return nil if min_arg > max_arg
264
+
265
+ case method_name
266
+ when :between? then range_between(range, min_arg, max_arg)
267
+ when :clamp then range_clamp(range, min_arg, max_arg)
268
+ end
269
+ end
270
+
271
+ def single_integer_arg(values)
272
+ return nil unless values.is_a?(Array) && values.size == 1
273
+
274
+ v = values.first
275
+ v.is_a?(Integer) ? v : nil
276
+ end
277
+
278
+ # `int<a,b>.between?(min, max)`:
279
+ # - Constant[true] when [a,b] ⊆ [min,max] (and finite).
280
+ # - Constant[false] when [a,b] ∩ [min,max] is empty.
281
+ # - bool union otherwise.
282
+ def range_between(range, min_arg, max_arg)
283
+ return Type::Combinator.constant_of(false) if range.upper < min_arg || range.lower > max_arg
284
+
285
+ return Type::Combinator.constant_of(true) if range.finite? && range.min >= min_arg && range.max <= max_arg
286
+
287
+ bool_union
288
+ end
289
+
290
+ # `int<a,b>.clamp(min, max)`:
291
+ # - new_lower = max(a, min), new_upper = min(b, max).
292
+ # - When new_lower > new_upper the bracket excluded the
293
+ # range entirely; the call still returns one of the
294
+ # bracket bounds at runtime, but Rigor is strictly less
295
+ # precise here than Ruby — decline so the RBS tier
296
+ # widens to plain Integer rather than the dispatcher
297
+ # inventing a value.
298
+ def range_clamp(range, min_arg, max_arg)
299
+ new_lower = clamp_lower_bound(range.lower, min_arg)
300
+ new_upper = clamp_upper_bound(range.upper, max_arg)
301
+ return nil if new_lower.is_a?(Integer) && new_upper.is_a?(Integer) && new_lower > new_upper
302
+
303
+ build_integer_range(new_lower, new_upper)
304
+ end
305
+
306
+ def clamp_lower_bound(range_lower, bracket_min)
307
+ return bracket_min if range_lower == -Float::INFINITY
308
+
309
+ [range_lower, bracket_min].max
310
+ end
311
+
312
+ def clamp_upper_bound(range_upper, bracket_max)
313
+ return bracket_max if range_upper == Float::INFINITY
314
+
315
+ [range_upper, bracket_max].min
316
+ end
317
+
248
318
  def try_fold_ternary_set(receiver_values, method_name, arg_sets)
249
319
  total = receiver_values.size * arg_sets[0].size * arg_sets[1].size
250
320
  return nil if total > UNION_FOLD_INPUT_LIMIT
@@ -760,7 +830,8 @@ module Rigor
760
830
  [DateTime, [Builtins::DATE_CATALOG, "DateTime"]],
761
831
  [Date, [Builtins::DATE_CATALOG, "Date"]],
762
832
  [Rational, [Builtins::RATIONAL_CATALOG, "Rational"]],
763
- [Complex, [Builtins::COMPLEX_CATALOG, "Complex"]]
833
+ [Complex, [Builtins::COMPLEX_CATALOG, "Complex"]],
834
+ [Pathname, [Builtins::PATHNAME_CATALOG, "Pathname"]]
764
835
  ].freeze
765
836
  private_constant :CATALOG_BY_CLASS
766
837
 
@@ -5,6 +5,7 @@ require_relative "method_dispatcher/constant_folding"
5
5
  require_relative "method_dispatcher/shape_dispatch"
6
6
  require_relative "method_dispatcher/rbs_dispatch"
7
7
  require_relative "method_dispatcher/iterator_dispatch"
8
+ require_relative "method_dispatcher/block_folding"
8
9
  require_relative "method_dispatcher/file_folding"
9
10
  require_relative "method_dispatcher/kernel_dispatch"
10
11
 
@@ -59,7 +60,7 @@ module Rigor
59
60
  def dispatch(receiver_type:, method_name:, arg_types:, block_type: nil, environment: nil)
60
61
  return nil if receiver_type.nil?
61
62
 
62
- precise = dispatch_precise_tiers(receiver_type, method_name, arg_types)
63
+ precise = dispatch_precise_tiers(receiver_type, method_name, arg_types, block_type)
63
64
  return precise if precise
64
65
 
65
66
  rbs_result = RbsDispatch.try_dispatch(
@@ -83,19 +84,28 @@ module Rigor
83
84
  end
84
85
 
85
86
  # Runs the precision tiers (constant fold, shape dispatch,
86
- # file-path fold) in order and returns the first non-nil
87
- # answer. Each tier owns its own receiver/argument shape
88
- # checks; a tier that does not recognise the receiver returns
89
- # nil so the next tier can try. The RBS tier sits below this
90
- # chain and is invoked by the outer `dispatch` method.
91
- def dispatch_precise_tiers(receiver_type, method_name, arg_types)
87
+ # file-path fold, block fold) in order and returns the first
88
+ # non-nil answer. Each tier owns its own receiver/argument
89
+ # shape checks; a tier that does not recognise the receiver
90
+ # returns nil so the next tier can try. The RBS tier sits
91
+ # below this chain and is invoked by the outer `dispatch`
92
+ # method.
93
+ #
94
+ # `BlockFolding` runs last among the precision tiers because
95
+ # its rules apply only to block-taking calls, so the cheaper
96
+ # arity-based fold tiers above it filter out the common
97
+ # cases first. When `block_type` is nil the tier is a no-op.
98
+ def dispatch_precise_tiers(receiver_type, method_name, arg_types, block_type = nil)
92
99
  meta_result = try_meta_introspection(receiver_type, method_name)
93
100
  return meta_result if meta_result
94
101
 
95
102
  ConstantFolding.try_fold(receiver: receiver_type, method_name: method_name, args: arg_types) ||
96
103
  ShapeDispatch.try_dispatch(receiver: receiver_type, method_name: method_name, args: arg_types) ||
97
104
  FileFolding.try_dispatch(receiver: receiver_type, method_name: method_name, args: arg_types) ||
98
- KernelDispatch.try_dispatch(receiver: receiver_type, method_name: method_name, args: arg_types)
105
+ KernelDispatch.try_dispatch(receiver: receiver_type, method_name: method_name, args: arg_types) ||
106
+ BlockFolding.try_fold(
107
+ receiver: receiver_type, method_name: method_name, args: arg_types, block_type: block_type
108
+ )
99
109
  end
100
110
 
101
111
  def try_user_class_fallback(receiver_type, method_name, arg_types, environment, block_type)
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.0.5"
4
+ VERSION = "0.0.6"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -169,6 +169,7 @@ files:
169
169
  - data/builtins/ruby_core/hash.yml
170
170
  - data/builtins/ruby_core/io.yml
171
171
  - data/builtins/ruby_core/numeric.yml
172
+ - data/builtins/ruby_core/pathname.yml
172
173
  - data/builtins/ruby_core/range.yml
173
174
  - data/builtins/ruby_core/rational.yml
174
175
  - data/builtins/ruby_core/set.yml
@@ -205,6 +206,7 @@ files:
205
206
  - lib/rigor/inference/builtins/hash_catalog.rb
206
207
  - lib/rigor/inference/builtins/method_catalog.rb
207
208
  - lib/rigor/inference/builtins/numeric_catalog.rb
209
+ - lib/rigor/inference/builtins/pathname_catalog.rb
208
210
  - lib/rigor/inference/builtins/range_catalog.rb
209
211
  - lib/rigor/inference/builtins/rational_catalog.rb
210
212
  - lib/rigor/inference/builtins/set_catalog.rb
@@ -216,6 +218,7 @@ files:
216
218
  - lib/rigor/inference/fallback.rb
217
219
  - lib/rigor/inference/fallback_tracer.rb
218
220
  - lib/rigor/inference/method_dispatcher.rb
221
+ - lib/rigor/inference/method_dispatcher/block_folding.rb
219
222
  - lib/rigor/inference/method_dispatcher/constant_folding.rb
220
223
  - lib/rigor/inference/method_dispatcher/file_folding.rb
221
224
  - lib/rigor/inference/method_dispatcher/iterator_dispatch.rb