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
|
@@ -155,6 +155,61 @@ module Rigor
|
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
+
# Integer-comparison fragment of `type` against an Integer
|
|
159
|
+
# literal `bound`. Narrows the receiver of `x < n`, `x <= n`,
|
|
160
|
+
# `x > n`, `x >= n` (and the reversed forms) to the subset of
|
|
161
|
+
# the existing domain that satisfies the comparison. Hooks in:
|
|
162
|
+
# - `Constant<Integer>` is preserved when it satisfies the
|
|
163
|
+
# comparison, otherwise collapsed to `Bot`.
|
|
164
|
+
# - `IntegerRange[a..b]` becomes the intersection with the
|
|
165
|
+
# half-line implied by the comparison; an empty intersection
|
|
166
|
+
# collapses to `Bot`, a single-point intersection collapses
|
|
167
|
+
# to `Constant<Integer>`.
|
|
168
|
+
# - `Nominal[Integer]` becomes the half-line itself (e.g.
|
|
169
|
+
# `x > 0` on `Nominal[Integer]` is `positive_int`).
|
|
170
|
+
# - `Union` narrows each member independently.
|
|
171
|
+
# - Other carriers (Float, String, Top, Dynamic) flow through
|
|
172
|
+
# unchanged: the analyzer does not have a Float-range carrier
|
|
173
|
+
# today, and no other carrier participates in numeric ordering.
|
|
174
|
+
def narrow_integer_comparison(type, comparator, bound)
|
|
175
|
+
return type unless bound.is_a?(Integer) && %i[< <= > >=].include?(comparator)
|
|
176
|
+
|
|
177
|
+
narrow_integer_comparison_dispatch(type, comparator, bound)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Equality fragment of `type` against an Integer `value`.
|
|
181
|
+
# `Constant<Integer>` is preserved when it equals `value`,
|
|
182
|
+
# otherwise collapses to `Bot`. `IntegerRange` covers? `value`
|
|
183
|
+
# narrows to `Constant[value]`; an out-of-range comparison
|
|
184
|
+
# collapses to `Bot`. `Nominal[Integer]` narrows to
|
|
185
|
+
# `Constant[value]`. `Union` narrows each member.
|
|
186
|
+
def narrow_integer_equal(type, value)
|
|
187
|
+
return type unless value.is_a?(Integer)
|
|
188
|
+
|
|
189
|
+
narrow_integer_equal_dispatch(type, value)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Complement of {.narrow_integer_equal}. Removes a single
|
|
193
|
+
# integer value from the domain when one endpoint of an
|
|
194
|
+
# `IntegerRange` is exactly that value (so the result stays a
|
|
195
|
+
# contiguous range). Domains where the value sits strictly
|
|
196
|
+
# between the endpoints stay unchanged: punching a hole would
|
|
197
|
+
# require a two-piece carrier the lattice does not yet model.
|
|
198
|
+
def narrow_integer_not_equal(type, value)
|
|
199
|
+
return type unless value.is_a?(Integer)
|
|
200
|
+
|
|
201
|
+
case type
|
|
202
|
+
when Type::Constant
|
|
203
|
+
type.value == value ? Type::Combinator.bot : type
|
|
204
|
+
when Type::IntegerRange
|
|
205
|
+
narrow_integer_range_not_equal(type, value)
|
|
206
|
+
when Type::Union
|
|
207
|
+
Type::Combinator.union(*type.members.map { |m| narrow_integer_not_equal(m, value) })
|
|
208
|
+
else
|
|
209
|
+
type
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
158
213
|
# Class-membership fragment of `type`: the subset whose
|
|
159
214
|
# inhabitants are instances of `class_name` (or its subclasses
|
|
160
215
|
# when `exact: false`). `class_name` is the qualified name of
|
|
@@ -452,7 +507,21 @@ module Rigor
|
|
|
452
507
|
end
|
|
453
508
|
end
|
|
454
509
|
|
|
510
|
+
ZERO_CLASS_PREDICATES = %i[positive? negative? zero? nonzero?].freeze
|
|
511
|
+
COMPARISON_OPERATORS = %i[< <= > >=].freeze
|
|
512
|
+
private_constant :ZERO_CLASS_PREDICATES, :COMPARISON_OPERATORS
|
|
513
|
+
|
|
455
514
|
def dispatch_call(node, scope, name)
|
|
515
|
+
return dispatch_call_simple(node, scope, name) if simple_dispatch_name?(name)
|
|
516
|
+
|
|
517
|
+
dispatch_call_numeric(node, scope, name)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def simple_dispatch_name?(name)
|
|
521
|
+
%i[nil? ! is_a? kind_of? instance_of? == != ===].include?(name)
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def dispatch_call_simple(node, scope, name)
|
|
456
525
|
case name
|
|
457
526
|
when :nil?, :! then dispatch_unary_predicate(node, scope, name)
|
|
458
527
|
when :is_a?, :kind_of? then analyse_class_predicate(node, scope, exact: false)
|
|
@@ -462,6 +531,92 @@ module Rigor
|
|
|
462
531
|
end
|
|
463
532
|
end
|
|
464
533
|
|
|
534
|
+
def dispatch_call_numeric(node, scope, name)
|
|
535
|
+
if COMPARISON_OPERATORS.include?(name)
|
|
536
|
+
analyse_comparison_predicate(node, scope, comparator: name)
|
|
537
|
+
elsif ZERO_CLASS_PREDICATES.include?(name)
|
|
538
|
+
analyse_zero_class_predicate(node, scope, predicate: name)
|
|
539
|
+
elsif name == :between?
|
|
540
|
+
analyse_between_predicate(node, scope)
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# `:positive?` / `:negative?` / `:zero?` / `:nonzero?` are
|
|
545
|
+
# zero-arg predicates on `Numeric`. We model them as
|
|
546
|
+
# comparisons against the literal 0 so the existing range
|
|
547
|
+
# narrowing handles them uniformly.
|
|
548
|
+
ZERO_CLASS_PREDICATE_RULES = {
|
|
549
|
+
positive?: { truthy: [:>, 0], falsey: [:<=, 0] },
|
|
550
|
+
negative?: { truthy: [:<, 0], falsey: [:>=, 0] },
|
|
551
|
+
zero?: { truthy: [:eq, 0], falsey: [:ne, 0] },
|
|
552
|
+
nonzero?: { truthy: [:ne, 0], falsey: [:eq, 0] }
|
|
553
|
+
}.freeze
|
|
554
|
+
private_constant :ZERO_CLASS_PREDICATE_RULES
|
|
555
|
+
|
|
556
|
+
def analyse_zero_class_predicate(node, scope, predicate:)
|
|
557
|
+
return nil unless argument_free?(node)
|
|
558
|
+
return nil unless node.receiver.is_a?(Prism::LocalVariableReadNode)
|
|
559
|
+
|
|
560
|
+
local_name = node.receiver.name
|
|
561
|
+
current = scope.local(local_name)
|
|
562
|
+
return nil if current.nil?
|
|
563
|
+
|
|
564
|
+
rules = ZERO_CLASS_PREDICATE_RULES[predicate]
|
|
565
|
+
truthy = apply_zero_rule(current, rules[:truthy])
|
|
566
|
+
falsey = apply_zero_rule(current, rules[:falsey])
|
|
567
|
+
[scope.with_local(local_name, truthy), scope.with_local(local_name, falsey)]
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def apply_zero_rule(type, rule)
|
|
571
|
+
case rule[0]
|
|
572
|
+
when :eq then narrow_integer_equal(type, rule[1])
|
|
573
|
+
when :ne then narrow_integer_not_equal(type, rule[1])
|
|
574
|
+
else
|
|
575
|
+
narrow_integer_comparison(type, rule[0], rule[1])
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# `x.between?(a, b)` truthy edge narrows to
|
|
580
|
+
# `narrow_integer_comparison(>=, a)` ∩
|
|
581
|
+
# `narrow_integer_comparison(<=, b)`. The falsey edge is left
|
|
582
|
+
# unchanged because the complement is a two-piece domain
|
|
583
|
+
# (`x < a || x > b`) that the lattice cannot express
|
|
584
|
+
# precisely. `a` and `b` MUST both be integer literals.
|
|
585
|
+
def analyse_between_predicate(node, scope)
|
|
586
|
+
return nil unless node.receiver.is_a?(Prism::LocalVariableReadNode)
|
|
587
|
+
return nil if node.arguments.nil?
|
|
588
|
+
return nil unless node.arguments.arguments.size == 2
|
|
589
|
+
|
|
590
|
+
low, high = node.arguments.arguments
|
|
591
|
+
return nil unless low.is_a?(Prism::IntegerNode) && high.is_a?(Prism::IntegerNode)
|
|
592
|
+
|
|
593
|
+
local_name = node.receiver.name
|
|
594
|
+
current = scope.local(local_name)
|
|
595
|
+
return nil if current.nil?
|
|
596
|
+
|
|
597
|
+
truthy = narrow_integer_comparison(
|
|
598
|
+
narrow_integer_comparison(current, :>=, low.value),
|
|
599
|
+
:<=, high.value
|
|
600
|
+
)
|
|
601
|
+
[scope.with_local(local_name, truthy), scope]
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# Helper for {.narrow_integer_not_equal}. Only adjusts when the
|
|
605
|
+
# value sits exactly on one endpoint, so the result stays
|
|
606
|
+
# contiguous; otherwise the input range is preserved.
|
|
607
|
+
def narrow_integer_range_not_equal(range, value)
|
|
608
|
+
return range if range.lower > value || range.upper < value
|
|
609
|
+
return Type::Combinator.bot if single_point_range_equal?(range, value)
|
|
610
|
+
return build_narrowing_integer_range(value + 1, range.upper) if range.lower == value
|
|
611
|
+
return build_narrowing_integer_range(range.lower, value - 1) if range.upper == value
|
|
612
|
+
|
|
613
|
+
range
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def single_point_range_equal?(range, value)
|
|
617
|
+
range.finite? && range.min == value && range.max == value
|
|
618
|
+
end
|
|
619
|
+
|
|
465
620
|
def dispatch_unary_predicate(node, scope, name)
|
|
466
621
|
return nil unless argument_free?(node)
|
|
467
622
|
|
|
@@ -491,6 +646,126 @@ module Rigor
|
|
|
491
646
|
equality == :== ? [positive, negative] : [negative, positive]
|
|
492
647
|
end
|
|
493
648
|
|
|
649
|
+
# Comparison predicate analyser. Recognised shapes:
|
|
650
|
+
# x < Int x <= Int x > Int x >= Int
|
|
651
|
+
# Int < x Int <= x Int > x Int >= x
|
|
652
|
+
# The reversed (literal-on-left) form is normalised by
|
|
653
|
+
# transposing the operator so the receiver-local always
|
|
654
|
+
# appears on the left of the rule.
|
|
655
|
+
INVERT_COMPARISON_OP = { :< => :>=, :<= => :>, :> => :<=, :>= => :< }.freeze
|
|
656
|
+
REVERSE_COMPARISON_OP = { :< => :>, :<= => :>=, :> => :<, :>= => :<= }.freeze
|
|
657
|
+
private_constant :INVERT_COMPARISON_OP, :REVERSE_COMPARISON_OP
|
|
658
|
+
|
|
659
|
+
def analyse_comparison_predicate(node, scope, comparator:)
|
|
660
|
+
return nil if node.arguments.nil?
|
|
661
|
+
return nil unless node.arguments.arguments.size == 1
|
|
662
|
+
|
|
663
|
+
match = comparison_local_literal(node.receiver, node.arguments.arguments.first, comparator)
|
|
664
|
+
return nil if match.nil?
|
|
665
|
+
|
|
666
|
+
local_name, normalised_op, bound = match
|
|
667
|
+
current = scope.local(local_name)
|
|
668
|
+
return nil if current.nil?
|
|
669
|
+
|
|
670
|
+
truthy = narrow_integer_comparison(current, normalised_op, bound)
|
|
671
|
+
falsey = narrow_integer_comparison(current, INVERT_COMPARISON_OP[normalised_op], bound)
|
|
672
|
+
[scope.with_local(local_name, truthy), scope.with_local(local_name, falsey)]
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
def comparison_local_literal(left, right, comparator)
|
|
676
|
+
if left.is_a?(Prism::LocalVariableReadNode) && right.is_a?(Prism::IntegerNode)
|
|
677
|
+
return [left.name, comparator, right.value]
|
|
678
|
+
end
|
|
679
|
+
return nil unless right.is_a?(Prism::LocalVariableReadNode) && left.is_a?(Prism::IntegerNode)
|
|
680
|
+
|
|
681
|
+
[right.name, REVERSE_COMPARISON_OP[comparator], left.value]
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def narrow_integer_equal_dispatch(type, value)
|
|
685
|
+
case type
|
|
686
|
+
when Type::Constant then narrow_integer_equal_constant(type, value)
|
|
687
|
+
when Type::IntegerRange then narrow_integer_equal_range(type, value)
|
|
688
|
+
when Type::Nominal then narrow_integer_equal_nominal(type, value)
|
|
689
|
+
when Type::Union
|
|
690
|
+
Type::Combinator.union(*type.members.map { |m| narrow_integer_equal(m, value) })
|
|
691
|
+
else type
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def narrow_integer_equal_constant(constant, value)
|
|
696
|
+
constant.value == value ? constant : Type::Combinator.bot
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
def narrow_integer_equal_range(range, value)
|
|
700
|
+
range.covers?(value) ? Type::Combinator.constant_of(value) : Type::Combinator.bot
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def narrow_integer_equal_nominal(nominal, value)
|
|
704
|
+
return nominal unless nominal.class_name == "Integer" && nominal.type_args.empty?
|
|
705
|
+
|
|
706
|
+
Type::Combinator.constant_of(value)
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def narrow_integer_comparison_dispatch(type, comparator, bound)
|
|
710
|
+
case type
|
|
711
|
+
when Type::Constant
|
|
712
|
+
integer_constant_satisfies?(type.value, comparator, bound) ? type : Type::Combinator.bot
|
|
713
|
+
when Type::IntegerRange
|
|
714
|
+
intersect_integer_range(type, comparator, bound)
|
|
715
|
+
when Type::Nominal
|
|
716
|
+
narrow_integer_comparison_nominal(type, comparator, bound)
|
|
717
|
+
when Type::Union
|
|
718
|
+
Type::Combinator.union(
|
|
719
|
+
*type.members.map { |m| narrow_integer_comparison(m, comparator, bound) }
|
|
720
|
+
)
|
|
721
|
+
else
|
|
722
|
+
type
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
def narrow_integer_comparison_nominal(nominal, comparator, bound)
|
|
727
|
+
return nominal unless nominal.class_name == "Integer" && nominal.type_args.empty?
|
|
728
|
+
|
|
729
|
+
intersect_integer_range(Type::Combinator.universal_int, comparator, bound)
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def integer_constant_satisfies?(value, comparator, bound)
|
|
733
|
+
return false unless value.is_a?(Integer)
|
|
734
|
+
|
|
735
|
+
case comparator
|
|
736
|
+
when :< then value < bound
|
|
737
|
+
when :<= then value <= bound
|
|
738
|
+
when :> then value > bound
|
|
739
|
+
when :>= then value >= bound
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
def intersect_integer_range(range, comparator, bound)
|
|
744
|
+
new_lower, new_upper = comparison_endpoints(range, comparator, bound)
|
|
745
|
+
return Type::Combinator.bot if new_lower > new_upper
|
|
746
|
+
|
|
747
|
+
build_narrowing_integer_range(new_lower, new_upper)
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def comparison_endpoints(range, comparator, bound)
|
|
751
|
+
case comparator
|
|
752
|
+
when :< then [range.lower, [range.upper, bound - 1].min]
|
|
753
|
+
when :<= then [range.lower, [range.upper, bound].min]
|
|
754
|
+
when :> then [[range.lower, bound + 1].max, range.upper]
|
|
755
|
+
when :>= then [[range.lower, bound].max, range.upper]
|
|
756
|
+
end
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
def build_narrowing_integer_range(lower, upper)
|
|
760
|
+
min = lower == -Float::INFINITY ? Type::IntegerRange::NEG_INFINITY : Integer(lower)
|
|
761
|
+
max = upper == Float::INFINITY ? Type::IntegerRange::POS_INFINITY : Integer(upper)
|
|
762
|
+
if min.is_a?(Integer) && max.is_a?(Integer) && min == max
|
|
763
|
+
Type::Combinator.constant_of(min)
|
|
764
|
+
else
|
|
765
|
+
Type::Combinator.integer_range(min, max)
|
|
766
|
+
end
|
|
767
|
+
end
|
|
768
|
+
|
|
494
769
|
def equality_local_literal(left, right, scope)
|
|
495
770
|
if left.is_a?(Prism::LocalVariableReadNode)
|
|
496
771
|
literal = static_literal_type(right, scope)
|
|
@@ -754,6 +1029,8 @@ module Rigor
|
|
|
754
1029
|
# the effect's `negative?` flag. Shared between
|
|
755
1030
|
# predicate-if-* and assert-if-* application paths.
|
|
756
1031
|
def narrow_for_effect(current, effect, environment)
|
|
1032
|
+
return effect.refinement_type if effect.respond_to?(:refinement?) && effect.refinement?
|
|
1033
|
+
|
|
757
1034
|
if effect.negative?
|
|
758
1035
|
narrow_not_class(current, effect.class_name, exact: false, environment: environment)
|
|
759
1036
|
else
|
|
@@ -850,10 +1127,11 @@ module Rigor
|
|
|
850
1127
|
fully_narrowable = true
|
|
851
1128
|
|
|
852
1129
|
conditions.each do |condition|
|
|
853
|
-
|
|
854
|
-
if
|
|
855
|
-
truthy_members <<
|
|
856
|
-
falsey_type =
|
|
1130
|
+
applied = apply_case_when_condition(scope, current, condition, falsey_type)
|
|
1131
|
+
if applied
|
|
1132
|
+
truthy_members << applied[:truthy]
|
|
1133
|
+
falsey_type = applied[:falsey]
|
|
1134
|
+
fully_narrowable &&= applied[:fully_narrowable]
|
|
857
1135
|
else
|
|
858
1136
|
fully_narrowable = false
|
|
859
1137
|
end
|
|
@@ -866,6 +1144,100 @@ module Rigor
|
|
|
866
1144
|
]
|
|
867
1145
|
end
|
|
868
1146
|
|
|
1147
|
+
# Per-condition rule. Returns `nil` when the condition shape
|
|
1148
|
+
# is not recognised (caller marks `fully_narrowable = false`),
|
|
1149
|
+
# or `{truthy:, falsey:, fully_narrowable:}` when it is.
|
|
1150
|
+
def apply_case_when_condition(scope, current, condition, falsey_acc)
|
|
1151
|
+
int_range = case_equality_integer_range(condition)
|
|
1152
|
+
return integer_range_when_result(current, int_range, falsey_acc) if int_range && integer_rooted_type?(current)
|
|
1153
|
+
|
|
1154
|
+
int_literal = case_equality_integer_literal(condition)
|
|
1155
|
+
if int_literal && integer_rooted_type?(current)
|
|
1156
|
+
return integer_literal_when_result(current, int_literal, falsey_acc)
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
target = static_class_name(condition) || case_equality_target_class(condition)
|
|
1160
|
+
return class_when_result(scope, current, target, falsey_acc) if target
|
|
1161
|
+
|
|
1162
|
+
nil
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
def case_equality_integer_literal(condition)
|
|
1166
|
+
condition = unwrap_parens(condition)
|
|
1167
|
+
condition.is_a?(Prism::IntegerNode) ? condition.value : nil
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
def integer_literal_when_result(current, value, falsey_acc)
|
|
1171
|
+
# `case n when k` is `k === n` which for Integer is value
|
|
1172
|
+
# equality. The truthy edge collapses the local to
|
|
1173
|
+
# `Constant[k]`; the falsey edge tightens via
|
|
1174
|
+
# `narrow_integer_not_equal` (only effective when k sits
|
|
1175
|
+
# at one endpoint of the current range).
|
|
1176
|
+
{
|
|
1177
|
+
truthy: narrow_integer_equal(current, value),
|
|
1178
|
+
falsey: narrow_integer_not_equal(falsey_acc, value),
|
|
1179
|
+
fully_narrowable: true
|
|
1180
|
+
}
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
def integer_range_when_result(current, range_pair, falsey_acc)
|
|
1184
|
+
low, high = range_pair
|
|
1185
|
+
truthy = narrow_integer_comparison(
|
|
1186
|
+
narrow_integer_comparison(current, :>=, low),
|
|
1187
|
+
:<=, high
|
|
1188
|
+
)
|
|
1189
|
+
# The falsey edge of `n in [a, b]` is two-piece; we cannot
|
|
1190
|
+
# express the complement precisely with a single carrier,
|
|
1191
|
+
# so keep the accumulator unchanged. `fully_narrowable: false`
|
|
1192
|
+
# forces the else-branch to see `current` (the unmodified
|
|
1193
|
+
# entry type), which mirrors `between?` falsey behaviour.
|
|
1194
|
+
{ truthy: truthy, falsey: falsey_acc, fully_narrowable: false }
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1197
|
+
def class_when_result(scope, current, target, falsey_acc)
|
|
1198
|
+
{
|
|
1199
|
+
truthy: narrow_class(current, target, exact: false, environment: scope.environment),
|
|
1200
|
+
falsey: narrow_not_class(falsey_acc, target, exact: false, environment: scope.environment),
|
|
1201
|
+
fully_narrowable: true
|
|
1202
|
+
}
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
# Returns `[low, high]` for a `Prism::RangeNode` whose
|
|
1206
|
+
# endpoints are both `Prism::IntegerNode` literals, with
|
|
1207
|
+
# `..`/`...` exclusivity respected. Open-ended ranges use
|
|
1208
|
+
# the symbolic infinities so the existing comparison
|
|
1209
|
+
# narrowing tier handles them. Returns `nil` for any other
|
|
1210
|
+
# shape (Float endpoints, String endpoints, dynamic
|
|
1211
|
+
# expressions).
|
|
1212
|
+
def case_equality_integer_range(condition)
|
|
1213
|
+
condition = unwrap_parens(condition)
|
|
1214
|
+
return nil unless condition.is_a?(Prism::RangeNode)
|
|
1215
|
+
|
|
1216
|
+
low = integer_range_endpoint(condition.left, default: Type::IntegerRange::NEG_INFINITY)
|
|
1217
|
+
high = integer_range_endpoint(condition.right, default: Type::IntegerRange::POS_INFINITY)
|
|
1218
|
+
return nil if low.nil? || high.nil?
|
|
1219
|
+
|
|
1220
|
+
high -= 1 if condition.exclude_end? && high.is_a?(Integer)
|
|
1221
|
+
[low, high]
|
|
1222
|
+
end
|
|
1223
|
+
|
|
1224
|
+
def integer_range_endpoint(node, default:)
|
|
1225
|
+
return default if node.nil?
|
|
1226
|
+
return node.value if node.is_a?(Prism::IntegerNode)
|
|
1227
|
+
|
|
1228
|
+
nil
|
|
1229
|
+
end
|
|
1230
|
+
|
|
1231
|
+
def integer_rooted_type?(type)
|
|
1232
|
+
case type
|
|
1233
|
+
when Type::Constant then type.value.is_a?(Integer)
|
|
1234
|
+
when Type::IntegerRange then true
|
|
1235
|
+
when Type::Nominal then type.class_name == "Integer" && type.type_args.empty?
|
|
1236
|
+
when Type::Union then type.members.all? { |m| integer_rooted_type?(m) }
|
|
1237
|
+
else false
|
|
1238
|
+
end
|
|
1239
|
+
end
|
|
1240
|
+
|
|
869
1241
|
def range_target_class(range_node)
|
|
870
1242
|
left = range_node.left
|
|
871
1243
|
right = range_node.right
|
|
@@ -418,11 +418,19 @@ module Rigor
|
|
|
418
418
|
end
|
|
419
419
|
end
|
|
420
420
|
|
|
421
|
+
# v0.0.3 A — sentinel key under which `record_def_node`
|
|
422
|
+
# files DefNodes that live outside any class / module
|
|
423
|
+
# body (top-level helpers, `def`s nested inside DSL
|
|
424
|
+
# blocks like `RSpec.describe ... do; def helper; end`).
|
|
425
|
+
# Looked up by `Scope#top_level_def_for` to give
|
|
426
|
+
# implicit-self calls priority over RBS dispatch when
|
|
427
|
+
# the file defines a same-named local method.
|
|
428
|
+
TOP_LEVEL_DEF_KEY = "<toplevel>"
|
|
429
|
+
|
|
421
430
|
def record_def_node(def_node, qualified_prefix, in_singleton_class, accumulator)
|
|
422
|
-
return if qualified_prefix.empty?
|
|
423
431
|
return if def_node.receiver.is_a?(Prism::SelfNode) || in_singleton_class
|
|
424
432
|
|
|
425
|
-
class_name = qualified_prefix.join("::")
|
|
433
|
+
class_name = qualified_prefix.empty? ? TOP_LEVEL_DEF_KEY : qualified_prefix.join("::")
|
|
426
434
|
accumulator[class_name] ||= {}
|
|
427
435
|
accumulator[class_name][def_node.name] = def_node
|
|
428
436
|
end
|