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.
- checksums.yaml +4 -4
- data/data/builtins/ruby_core/pathname.yml +1067 -0
- data/lib/rigor/inference/builtins/pathname_catalog.rb +35 -0
- data/lib/rigor/inference/expression_typer.rb +285 -23
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +322 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +78 -7
- data/lib/rigor/inference/method_dispatcher.rb +18 -8
- data/lib/rigor/version.rb +1 -1
- metadata +4 -1
|
@@ -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.
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
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
|
|
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
|
|
87
|
-
# answer. Each tier owns its own receiver/argument
|
|
88
|
-
# checks; a tier that does not recognise the receiver
|
|
89
|
-
# nil so the next tier can try. The RBS tier sits
|
|
90
|
-
# chain and is invoked by the outer `dispatch`
|
|
91
|
-
|
|
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
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.
|
|
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
|