rigortype 0.0.5 → 0.0.7
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/analysis/check_rules.rb +38 -41
- data/lib/rigor/builtins/imported_refinements.rb +93 -3
- data/lib/rigor/inference/builtins/pathname_catalog.rb +35 -0
- data/lib/rigor/inference/expression_typer.rb +310 -25
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +322 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +325 -8
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +45 -4
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +409 -0
- data/lib/rigor/inference/method_dispatcher.rb +88 -18
- data/lib/rigor/inference/method_parameter_binder.rb +3 -5
- data/lib/rigor/inference/narrowing.rb +38 -6
- data/lib/rigor/inference/statement_evaluator.rb +5 -7
- data/lib/rigor/reflection.rb +203 -0
- data/lib/rigor/type/combinator.rb +244 -1
- data/lib/rigor/type/constant.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/sig/rigor/reflection.rbs +17 -0
- data/sig/rigor/type.rbs +5 -0
- metadata +6 -1
|
@@ -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
|
|
@@ -108,6 +109,16 @@ module Rigor
|
|
|
108
109
|
|
|
109
110
|
# @return [Rigor::Type::Constant, Rigor::Type::Union, Rigor::Type::IntegerRange, nil]
|
|
110
111
|
def try_fold(receiver:, method_name:, args:)
|
|
112
|
+
# v0.0.7 — `String#%` against a `Tuple` / `HashShape`
|
|
113
|
+
# argument runs Ruby's format-string engine when both
|
|
114
|
+
# sides are statically constant. The standard
|
|
115
|
+
# `numeric_set_of` path bails on Tuple / HashShape
|
|
116
|
+
# arguments because they are not scalar-Constant
|
|
117
|
+
# carriers, so the special-case sits ahead of the
|
|
118
|
+
# numeric path.
|
|
119
|
+
format_lift = try_fold_string_format(receiver, method_name, args)
|
|
120
|
+
return format_lift if format_lift
|
|
121
|
+
|
|
111
122
|
receiver_set = numeric_set_of(receiver)
|
|
112
123
|
return nil unless receiver_set
|
|
113
124
|
|
|
@@ -117,6 +128,59 @@ module Rigor
|
|
|
117
128
|
dispatch_by_arity(receiver_set, method_name, arg_sets)
|
|
118
129
|
end
|
|
119
130
|
|
|
131
|
+
# `Constant<String> % …` — runs the actual `String#%`
|
|
132
|
+
# operation when both sides are statically known. The
|
|
133
|
+
# argument may be:
|
|
134
|
+
# - A `Type::Constant` whose value is a scalar (Integer
|
|
135
|
+
# / Float / String / Symbol). Already handled by the
|
|
136
|
+
# numeric path; this method declines so the standard
|
|
137
|
+
# binary path picks it up.
|
|
138
|
+
# - A `Type::Tuple` whose elements are all `Constant`.
|
|
139
|
+
# Materialises the elements as a Ruby Array and runs
|
|
140
|
+
# the format.
|
|
141
|
+
# - A `Type::HashShape` with no optional keys whose
|
|
142
|
+
# values are all `Constant`. Materialises a Ruby Hash
|
|
143
|
+
# and runs the format. Symbol keys are kept as
|
|
144
|
+
# Symbols (matching Ruby's `%{key}` resolution).
|
|
145
|
+
# Anything else declines so the RBS tier widens.
|
|
146
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
147
|
+
def try_fold_string_format(receiver, method_name, args)
|
|
148
|
+
return nil unless method_name == :%
|
|
149
|
+
return nil unless args.size == 1
|
|
150
|
+
return nil unless receiver.is_a?(Type::Constant) && receiver.value.is_a?(String)
|
|
151
|
+
|
|
152
|
+
arg = args.first
|
|
153
|
+
ruby_arg = format_argument_value(arg)
|
|
154
|
+
return nil if ruby_arg.nil?
|
|
155
|
+
|
|
156
|
+
result = receiver.value % ruby_arg
|
|
157
|
+
return nil unless foldable_constant_value?(result)
|
|
158
|
+
|
|
159
|
+
Type::Combinator.constant_of(result)
|
|
160
|
+
rescue StandardError
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
164
|
+
|
|
165
|
+
def format_argument_value(arg)
|
|
166
|
+
case arg
|
|
167
|
+
when Type::Tuple
|
|
168
|
+
return nil unless arg.elements.all?(Type::Constant)
|
|
169
|
+
|
|
170
|
+
arg.elements.map(&:value)
|
|
171
|
+
when Type::HashShape
|
|
172
|
+
hash_shape_format_value(arg)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def hash_shape_format_value(shape)
|
|
177
|
+
return nil unless shape.closed?
|
|
178
|
+
return nil unless shape.optional_keys.empty?
|
|
179
|
+
return nil unless shape.pairs.values.all?(Type::Constant)
|
|
180
|
+
|
|
181
|
+
shape.pairs.transform_values(&:value)
|
|
182
|
+
end
|
|
183
|
+
|
|
120
184
|
def dispatch_by_arity(receiver_set, method_name, arg_sets)
|
|
121
185
|
case arg_sets.size
|
|
122
186
|
when 0 then try_fold_unary(receiver_set, method_name)
|
|
@@ -205,7 +269,17 @@ module Rigor
|
|
|
205
269
|
[result]
|
|
206
270
|
end
|
|
207
271
|
|
|
272
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
208
273
|
def try_fold_unary_set(receiver_values, method_name)
|
|
274
|
+
range_lift = try_fold_range_constant_unary(receiver_values, method_name)
|
|
275
|
+
return range_lift if range_lift
|
|
276
|
+
|
|
277
|
+
string_lift = try_fold_string_array_unary(receiver_values, method_name)
|
|
278
|
+
return string_lift if string_lift
|
|
279
|
+
|
|
280
|
+
pathname_lift = try_fold_pathname_unary(receiver_values, method_name)
|
|
281
|
+
return pathname_lift if pathname_lift
|
|
282
|
+
|
|
209
283
|
# Type-level allow check on every receiver. If one member's
|
|
210
284
|
# type does not have the method in its allow list (e.g.
|
|
211
285
|
# `Union[String, nil].nil?` — `:nil?` is not in
|
|
@@ -219,8 +293,68 @@ module Rigor
|
|
|
219
293
|
end
|
|
220
294
|
build_constant_type(results, source: receiver_values)
|
|
221
295
|
end
|
|
296
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
297
|
+
|
|
298
|
+
# v0.0.7 — `Constant<Range>#to_a` and the no-arg
|
|
299
|
+
# `first` / `last` / `min` / `max` short-circuit through a
|
|
300
|
+
# Range-specific arm that catalog dispatch cannot reach:
|
|
301
|
+
# - `to_a` returns an Array (not foldable through
|
|
302
|
+
# `foldable_constant_value?`) — lift to `Tuple[Constant…]`
|
|
303
|
+
# when the cardinality fits within `RANGE_TO_A_LIMIT`.
|
|
304
|
+
# - `first` / `last` / `min` / `max` are catalog-classified
|
|
305
|
+
# `:block_dependent` because of the optional-block forms,
|
|
306
|
+
# but the no-arg form is pure for finite integer ranges.
|
|
307
|
+
#
|
|
308
|
+
# Only fires on a single-receiver Range with finite integer
|
|
309
|
+
# endpoints; mixed unions fall through so the existing
|
|
310
|
+
# union-of-Constants path keeps the rest of the arms.
|
|
311
|
+
RANGE_FOLD_METHODS = Set[:to_a, :first, :last, :min, :max, :count, :size, :length].freeze
|
|
312
|
+
RANGE_TO_A_LIMIT = 16
|
|
313
|
+
private_constant :RANGE_FOLD_METHODS, :RANGE_TO_A_LIMIT
|
|
314
|
+
|
|
315
|
+
def try_fold_range_constant_unary(receiver_values, method_name)
|
|
316
|
+
return nil unless RANGE_FOLD_METHODS.include?(method_name)
|
|
317
|
+
return nil unless receiver_values.size == 1
|
|
318
|
+
|
|
319
|
+
range = receiver_values.first
|
|
320
|
+
return nil unless range.is_a?(Range)
|
|
321
|
+
return nil unless range.begin.is_a?(Integer) && range.end.is_a?(Integer)
|
|
322
|
+
|
|
323
|
+
range_constant_unary(range, method_name)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def range_constant_unary(range, method_name)
|
|
327
|
+
case method_name
|
|
328
|
+
when :to_a then range_to_a_tuple(range)
|
|
329
|
+
when :first, :min then range_endpoint_constant(range, :first)
|
|
330
|
+
when :last, :max then range_endpoint_constant(range, :last)
|
|
331
|
+
when :count, :size, :length then Type::Combinator.constant_of(range.to_a.size)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def range_to_a_tuple(range)
|
|
336
|
+
values = range.to_a
|
|
337
|
+
return Type::Combinator.tuple_of if values.empty?
|
|
338
|
+
return nil if values.size > RANGE_TO_A_LIMIT
|
|
339
|
+
|
|
340
|
+
Type::Combinator.tuple_of(*values.map { |v| Type::Combinator.constant_of(v) })
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def range_endpoint_constant(range, edge)
|
|
344
|
+
values = range.to_a
|
|
345
|
+
return Type::Combinator.constant_of(nil) if values.empty?
|
|
222
346
|
|
|
347
|
+
Type::Combinator.constant_of(edge == :first ? values.first : values.last)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
223
351
|
def try_fold_binary_set(receiver_values, method_name, arg_values)
|
|
352
|
+
string_lift = try_fold_string_array_binary(receiver_values, method_name, arg_values)
|
|
353
|
+
return string_lift if string_lift
|
|
354
|
+
|
|
355
|
+
pathname_lift = try_fold_pathname_binary(receiver_values, method_name, arg_values)
|
|
356
|
+
return pathname_lift if pathname_lift
|
|
357
|
+
|
|
224
358
|
return nil if receiver_values.size * arg_values.size > UNION_FOLD_INPUT_LIMIT
|
|
225
359
|
return nil unless receiver_values.all? { |rv| binary_method_allowed?(rv, method_name) }
|
|
226
360
|
|
|
@@ -229,22 +363,204 @@ module Rigor
|
|
|
229
363
|
end
|
|
230
364
|
build_constant_type(results, source: receiver_values + arg_values)
|
|
231
365
|
end
|
|
366
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
367
|
+
|
|
368
|
+
# v0.0.7 — `Constant<String>#chars` / `bytes` / `lines` /
|
|
369
|
+
# `split` (no-arg) return a Ruby Array of foldable
|
|
370
|
+
# scalars; `foldable_constant_value?` rejects Array
|
|
371
|
+
# results, so the standard unary path declines. Lift the
|
|
372
|
+
# Array to a per-position `Tuple[Constant…]` directly,
|
|
373
|
+
# capped at `STRING_ARRAY_LIFT_LIMIT` to keep the result
|
|
374
|
+
# bounded for long strings.
|
|
375
|
+
STRING_ARRAY_UNARY_METHODS = Set[:chars, :bytes, :lines, :split].freeze
|
|
376
|
+
STRING_ARRAY_BINARY_METHODS = Set[:split, :scan].freeze
|
|
377
|
+
STRING_ARRAY_LIFT_LIMIT = 32
|
|
378
|
+
private_constant :STRING_ARRAY_UNARY_METHODS,
|
|
379
|
+
:STRING_ARRAY_BINARY_METHODS,
|
|
380
|
+
:STRING_ARRAY_LIFT_LIMIT
|
|
381
|
+
|
|
382
|
+
# v0.0.7 — `Constant<Pathname>` delegates to a curated set
|
|
383
|
+
# of pure path-manipulation methods. Pathname is immutable
|
|
384
|
+
# in Ruby (per its docstring) and the catalog classifies
|
|
385
|
+
# most methods `:dispatch` because the C body delegates to
|
|
386
|
+
# File / Dir / FileTest. The methods listed here are
|
|
387
|
+
# filesystem-independent — they read only `@path` — so
|
|
388
|
+
# invoking them at fold time produces a deterministic
|
|
389
|
+
# result regardless of the host filesystem state.
|
|
390
|
+
#
|
|
391
|
+
# Filesystem-touching methods (`exist?`, `file?`, `read`,
|
|
392
|
+
# `stat`, …) are intentionally NOT folded: their answer
|
|
393
|
+
# depends on the analysis machine's filesystem, which is
|
|
394
|
+
# neither stable nor relevant to the analyzed program.
|
|
395
|
+
PATHNAME_PURE_UNARY = Set[
|
|
396
|
+
:to_s, :to_path, :to_str,
|
|
397
|
+
:basename, :dirname, :extname, :cleanpath,
|
|
398
|
+
:parent, :sub_ext, :root?, :absolute?, :relative?,
|
|
399
|
+
:hash, :inspect
|
|
400
|
+
].freeze
|
|
401
|
+
PATHNAME_PURE_BINARY = Set[
|
|
402
|
+
:+, :join, :sub_ext, :<=>, :==, :eql?, :===,
|
|
403
|
+
:relative_path_from
|
|
404
|
+
].freeze
|
|
405
|
+
private_constant :PATHNAME_PURE_UNARY, :PATHNAME_PURE_BINARY
|
|
406
|
+
|
|
407
|
+
def try_fold_pathname_unary(receiver_values, method_name)
|
|
408
|
+
return nil unless PATHNAME_PURE_UNARY.include?(method_name)
|
|
409
|
+
return nil unless receiver_values.size == 1
|
|
410
|
+
|
|
411
|
+
receiver = receiver_values.first
|
|
412
|
+
return nil unless receiver.is_a?(Pathname)
|
|
413
|
+
|
|
414
|
+
result = receiver.public_send(method_name)
|
|
415
|
+
return nil unless foldable_constant_value?(result)
|
|
416
|
+
|
|
417
|
+
Type::Combinator.constant_of(result)
|
|
418
|
+
rescue StandardError
|
|
419
|
+
nil
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
423
|
+
def try_fold_pathname_binary(receiver_values, method_name, arg_values)
|
|
424
|
+
return nil unless PATHNAME_PURE_BINARY.include?(method_name)
|
|
425
|
+
return nil unless receiver_values.size == 1 && arg_values.size == 1
|
|
426
|
+
|
|
427
|
+
receiver = receiver_values.first
|
|
428
|
+
arg = arg_values.first
|
|
429
|
+
return nil unless receiver.is_a?(Pathname)
|
|
430
|
+
return nil unless arg.is_a?(Pathname) || arg.is_a?(String)
|
|
431
|
+
|
|
432
|
+
result = receiver.public_send(method_name, arg)
|
|
433
|
+
return nil unless foldable_constant_value?(result)
|
|
434
|
+
|
|
435
|
+
Type::Combinator.constant_of(result)
|
|
436
|
+
rescue StandardError
|
|
437
|
+
nil
|
|
438
|
+
end
|
|
439
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
440
|
+
|
|
441
|
+
def try_fold_string_array_unary(receiver_values, method_name)
|
|
442
|
+
return nil unless STRING_ARRAY_UNARY_METHODS.include?(method_name)
|
|
443
|
+
return nil unless receiver_values.size == 1
|
|
444
|
+
|
|
445
|
+
receiver = receiver_values.first
|
|
446
|
+
return nil unless receiver.is_a?(String)
|
|
447
|
+
|
|
448
|
+
lift_array_result(receiver.public_send(method_name))
|
|
449
|
+
rescue StandardError
|
|
450
|
+
nil
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# `Constant<String>#split(arg)` / `#scan(arg)` — lift the
|
|
454
|
+
# Array result to a Tuple when both sides are statically
|
|
455
|
+
# known and the cardinality fits.
|
|
456
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
457
|
+
def try_fold_string_array_binary(receiver_values, method_name, arg_values)
|
|
458
|
+
return nil unless STRING_ARRAY_BINARY_METHODS.include?(method_name)
|
|
459
|
+
return nil unless receiver_values.size == 1 && arg_values.size == 1
|
|
460
|
+
|
|
461
|
+
receiver = receiver_values.first
|
|
462
|
+
arg = arg_values.first
|
|
463
|
+
return nil unless receiver.is_a?(String)
|
|
464
|
+
return nil unless arg.is_a?(String) || arg.is_a?(Regexp)
|
|
465
|
+
|
|
466
|
+
lift_array_result(receiver.public_send(method_name, arg))
|
|
467
|
+
rescue StandardError
|
|
468
|
+
nil
|
|
469
|
+
end
|
|
470
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
471
|
+
|
|
472
|
+
def lift_array_result(result)
|
|
473
|
+
return nil unless result.is_a?(Array)
|
|
474
|
+
return nil if result.size > STRING_ARRAY_LIFT_LIMIT
|
|
475
|
+
return nil unless result.all? { |v| foldable_constant_value?(v) }
|
|
476
|
+
|
|
477
|
+
Type::Combinator.tuple_of(*result.map { |v| Type::Combinator.constant_of(v) })
|
|
478
|
+
end
|
|
232
479
|
|
|
233
480
|
# 2-arg fold dispatch. Used by `Comparable#between?(min, max)`,
|
|
234
481
|
# `Comparable#clamp(min, max)`, and `Integer#pow(exp, mod)` —
|
|
235
482
|
# methods the catalog classifies `:leaf` but that the prior
|
|
236
|
-
# 0/1-arg switch could not reach.
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
483
|
+
# 0/1-arg switch could not reach.
|
|
484
|
+
#
|
|
485
|
+
# v0.0.6 — IntegerRange-shaped receivers participate in
|
|
486
|
+
# `Comparable#between?` and `Comparable#clamp` folds.
|
|
487
|
+
# `int<a,b>.between?(min, max)` decides three-valued via
|
|
488
|
+
# the receiver's bounds against scalar args; `int<a,b>.clamp`
|
|
489
|
+
# narrows the receiver's bounds against the bracket. Other
|
|
490
|
+
# ternary methods over IntegerRange operands still decline.
|
|
241
491
|
def try_fold_ternary(receiver_set, method_name, arg_sets)
|
|
242
|
-
return
|
|
492
|
+
return try_fold_ternary_range(receiver_set, method_name, arg_sets) if receiver_set.is_a?(Type::IntegerRange)
|
|
243
493
|
return nil if arg_sets.any?(Type::IntegerRange)
|
|
244
494
|
|
|
245
495
|
try_fold_ternary_set(receiver_set, method_name, arg_sets)
|
|
246
496
|
end
|
|
247
497
|
|
|
498
|
+
# Receiver IntegerRange + two scalar `Constant<Integer>`
|
|
499
|
+
# args — the only IntegerRange-aware ternary fold today.
|
|
500
|
+
# `between?` returns Trinary truthiness over the bracket;
|
|
501
|
+
# `clamp` returns the intersected IntegerRange (or a
|
|
502
|
+
# collapsed Constant if the result pins a single point).
|
|
503
|
+
def try_fold_ternary_range(range, method_name, arg_sets)
|
|
504
|
+
return nil unless arg_sets.all?(Array)
|
|
505
|
+
|
|
506
|
+
min_arg = single_integer_arg(arg_sets[0])
|
|
507
|
+
max_arg = single_integer_arg(arg_sets[1])
|
|
508
|
+
return nil if min_arg.nil? || max_arg.nil?
|
|
509
|
+
return nil if min_arg > max_arg
|
|
510
|
+
|
|
511
|
+
case method_name
|
|
512
|
+
when :between? then range_between(range, min_arg, max_arg)
|
|
513
|
+
when :clamp then range_clamp(range, min_arg, max_arg)
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def single_integer_arg(values)
|
|
518
|
+
return nil unless values.is_a?(Array) && values.size == 1
|
|
519
|
+
|
|
520
|
+
v = values.first
|
|
521
|
+
v.is_a?(Integer) ? v : nil
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# `int<a,b>.between?(min, max)`:
|
|
525
|
+
# - Constant[true] when [a,b] ⊆ [min,max] (and finite).
|
|
526
|
+
# - Constant[false] when [a,b] ∩ [min,max] is empty.
|
|
527
|
+
# - bool union otherwise.
|
|
528
|
+
def range_between(range, min_arg, max_arg)
|
|
529
|
+
return Type::Combinator.constant_of(false) if range.upper < min_arg || range.lower > max_arg
|
|
530
|
+
|
|
531
|
+
return Type::Combinator.constant_of(true) if range.finite? && range.min >= min_arg && range.max <= max_arg
|
|
532
|
+
|
|
533
|
+
bool_union
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# `int<a,b>.clamp(min, max)`:
|
|
537
|
+
# - new_lower = max(a, min), new_upper = min(b, max).
|
|
538
|
+
# - When new_lower > new_upper the bracket excluded the
|
|
539
|
+
# range entirely; the call still returns one of the
|
|
540
|
+
# bracket bounds at runtime, but Rigor is strictly less
|
|
541
|
+
# precise here than Ruby — decline so the RBS tier
|
|
542
|
+
# widens to plain Integer rather than the dispatcher
|
|
543
|
+
# inventing a value.
|
|
544
|
+
def range_clamp(range, min_arg, max_arg)
|
|
545
|
+
new_lower = clamp_lower_bound(range.lower, min_arg)
|
|
546
|
+
new_upper = clamp_upper_bound(range.upper, max_arg)
|
|
547
|
+
return nil if new_lower.is_a?(Integer) && new_upper.is_a?(Integer) && new_lower > new_upper
|
|
548
|
+
|
|
549
|
+
build_integer_range(new_lower, new_upper)
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def clamp_lower_bound(range_lower, bracket_min)
|
|
553
|
+
return bracket_min if range_lower == -Float::INFINITY
|
|
554
|
+
|
|
555
|
+
[range_lower, bracket_min].max
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def clamp_upper_bound(range_upper, bracket_max)
|
|
559
|
+
return bracket_max if range_upper == Float::INFINITY
|
|
560
|
+
|
|
561
|
+
[range_upper, bracket_max].min
|
|
562
|
+
end
|
|
563
|
+
|
|
248
564
|
def try_fold_ternary_set(receiver_values, method_name, arg_sets)
|
|
249
565
|
total = receiver_values.size * arg_sets[0].size * arg_sets[1].size
|
|
250
566
|
return nil if total > UNION_FOLD_INPUT_LIMIT
|
|
@@ -760,7 +1076,8 @@ module Rigor
|
|
|
760
1076
|
[DateTime, [Builtins::DATE_CATALOG, "DateTime"]],
|
|
761
1077
|
[Date, [Builtins::DATE_CATALOG, "Date"]],
|
|
762
1078
|
[Rational, [Builtins::RATIONAL_CATALOG, "Rational"]],
|
|
763
|
-
[Complex, [Builtins::COMPLEX_CATALOG, "Complex"]]
|
|
1079
|
+
[Complex, [Builtins::COMPLEX_CATALOG, "Complex"]],
|
|
1080
|
+
[Pathname, [Builtins::PATHNAME_CATALOG, "Pathname"]]
|
|
764
1081
|
].freeze
|
|
765
1082
|
private_constant :CATALOG_BY_CLASS
|
|
766
1083
|
|
|
@@ -808,7 +1125,7 @@ module Rigor
|
|
|
808
1125
|
# round-trip through `Type::Combinator.constant_of`".
|
|
809
1126
|
def foldable_constant_value?(value)
|
|
810
1127
|
case value
|
|
811
|
-
when Integer, Float, String, Symbol, true, false, nil then true
|
|
1128
|
+
when Integer, Float, Rational, Complex, String, Symbol, Regexp, Pathname, true, false, nil then true
|
|
812
1129
|
else false
|
|
813
1130
|
end
|
|
814
1131
|
end
|
|
@@ -30,18 +30,59 @@ module Rigor
|
|
|
30
30
|
module KernelDispatch
|
|
31
31
|
module_function
|
|
32
32
|
|
|
33
|
+
# `Kernel#Rational` / `Kernel#Complex` constructor folds.
|
|
34
|
+
# When every argument is a `Type::Constant` whose value is
|
|
35
|
+
# numeric, we can run the actual Ruby constructor and lift
|
|
36
|
+
# the result into a `Constant<Rational>` / `Constant<Complex>`.
|
|
37
|
+
# The factory accepts the same shapes as Ruby:
|
|
38
|
+
# `Rational(a)`, `Rational(a, b)`, `Complex(a)`, `Complex(a, b)`.
|
|
39
|
+
NUMERIC_CONSTRUCTORS = {
|
|
40
|
+
Rational: ->(*args) { Rational(*args) },
|
|
41
|
+
Complex: ->(*args) { Complex(*args) }
|
|
42
|
+
}.freeze
|
|
43
|
+
private_constant :NUMERIC_CONSTRUCTORS
|
|
44
|
+
|
|
33
45
|
def try_dispatch(receiver:, method_name:, args:)
|
|
34
|
-
return nil unless method_name == :Array
|
|
35
|
-
return nil if args.length != 1
|
|
36
46
|
return nil if receiver.nil?
|
|
47
|
+
return try_array(args) if method_name == :Array
|
|
48
|
+
return try_numeric_constructor(method_name, args) if NUMERIC_CONSTRUCTORS.key?(method_name)
|
|
49
|
+
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def try_array(args)
|
|
54
|
+
return nil if args.length != 1
|
|
37
55
|
|
|
38
|
-
|
|
39
|
-
element = element_type_of(arg)
|
|
56
|
+
element = element_type_of(args.first)
|
|
40
57
|
return nil if element.nil?
|
|
41
58
|
|
|
42
59
|
Type::Combinator.nominal_of("Array", type_args: [element])
|
|
43
60
|
end
|
|
44
61
|
|
|
62
|
+
# `Rational(int)` / `Rational(num, den)` and `Complex(re)`
|
|
63
|
+
# / `Complex(re, im)` fold when every arg is a numeric
|
|
64
|
+
# Constant. The actual Ruby constructor runs at fold time
|
|
65
|
+
# (host-side), so the result respects Ruby's normalisation
|
|
66
|
+
# (`Rational(2, 4)` → `Rational(1, 2)`).
|
|
67
|
+
def try_numeric_constructor(method_name, args)
|
|
68
|
+
return nil unless [1, 2].include?(args.size)
|
|
69
|
+
return nil unless args.all? { |arg| numeric_constant?(arg) }
|
|
70
|
+
|
|
71
|
+
values = args.map(&:value)
|
|
72
|
+
result = NUMERIC_CONSTRUCTORS[method_name].call(*values)
|
|
73
|
+
Type::Combinator.constant_of(result)
|
|
74
|
+
rescue StandardError
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def numeric_constant?(type)
|
|
79
|
+
type.is_a?(Type::Constant) &&
|
|
80
|
+
(type.value.is_a?(Integer) ||
|
|
81
|
+
type.value.is_a?(Float) ||
|
|
82
|
+
type.value.is_a?(Rational) ||
|
|
83
|
+
type.value.is_a?(Complex))
|
|
84
|
+
end
|
|
85
|
+
|
|
45
86
|
# Computes the element type the argument contributes to the
|
|
46
87
|
# `Array(arg)` result, mirroring Ruby's coercion contract:
|
|
47
88
|
#
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../reflection"
|
|
3
4
|
require_relative "../../type"
|
|
4
5
|
require_relative "../../rbs_extended"
|
|
5
6
|
require_relative "../rbs_type_translator"
|
|
@@ -218,15 +219,9 @@ module Rigor
|
|
|
218
219
|
def lookup_method(environment, class_name, kind, method_name)
|
|
219
220
|
case kind
|
|
220
221
|
when :instance
|
|
221
|
-
|
|
222
|
-
class_name: class_name,
|
|
223
|
-
method_name: method_name
|
|
224
|
-
)
|
|
222
|
+
Rigor::Reflection.instance_method_definition(class_name, method_name, environment: environment)
|
|
225
223
|
when :singleton
|
|
226
|
-
|
|
227
|
-
class_name: class_name,
|
|
228
|
-
method_name: method_name
|
|
229
|
-
)
|
|
224
|
+
Rigor::Reflection.singleton_method_definition(class_name, method_name, environment: environment)
|
|
230
225
|
end
|
|
231
226
|
end
|
|
232
227
|
|
|
@@ -239,7 +234,7 @@ module Rigor
|
|
|
239
234
|
def build_type_vars(environment, class_name, receiver_args)
|
|
240
235
|
return {} if receiver_args.empty?
|
|
241
236
|
|
|
242
|
-
param_names =
|
|
237
|
+
param_names = Rigor::Reflection.class_type_param_names(class_name, environment: environment)
|
|
243
238
|
return {} if param_names.empty?
|
|
244
239
|
return {} if param_names.size != receiver_args.size
|
|
245
240
|
|