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
|
@@ -52,6 +52,12 @@ module Rigor
|
|
|
52
52
|
# Literals
|
|
53
53
|
Prism::IntegerNode => :type_of_literal_value,
|
|
54
54
|
Prism::FloatNode => :type_of_literal_value,
|
|
55
|
+
# `1i` / `2.5ri` lift via `node.value` which is already a
|
|
56
|
+
# `Complex` Ruby value; same for `1r` / `1.5r` whose
|
|
57
|
+
# value is a `Rational`. `Type::Constant` accepts both
|
|
58
|
+
# via `SCALAR_CLASSES`.
|
|
59
|
+
Prism::ImaginaryNode => :type_of_literal_value,
|
|
60
|
+
Prism::RationalNode => :type_of_literal_value,
|
|
55
61
|
Prism::SymbolNode => :symbol_type_for,
|
|
56
62
|
Prism::StringNode => :string_type_for,
|
|
57
63
|
Prism::TrueNode => :type_of_true,
|
|
@@ -401,7 +407,13 @@ module Rigor
|
|
|
401
407
|
# so callers stay backward compatible.
|
|
402
408
|
def type_of_hash(node)
|
|
403
409
|
elements = node.respond_to?(:elements) ? node.elements : []
|
|
404
|
-
|
|
410
|
+
# v0.0.7 — `{}` resolves to the empty `HashShape{}` carrier
|
|
411
|
+
# rather than `Nominal[Hash]`, mirroring the v0.0.6 empty-
|
|
412
|
+
# array literal change. Both forms erase to plain `Hash`,
|
|
413
|
+
# but `HashShape{}` pins the literal's known size (zero)
|
|
414
|
+
# so HashShape projections (`empty?`, `first`, `count`,
|
|
415
|
+
# …) fold against it.
|
|
416
|
+
return Type::Combinator.hash_shape_of({}) if elements.empty?
|
|
405
417
|
|
|
406
418
|
shape = static_hash_shape_for(elements)
|
|
407
419
|
return shape if shape
|
|
@@ -500,39 +512,99 @@ module Rigor
|
|
|
500
512
|
# rebinding is the StatementEvaluator's job (Slice 3 phase 2).
|
|
501
513
|
# Without an else clause the branch's implicit value is nil, which
|
|
502
514
|
# is included in the union.
|
|
515
|
+
#
|
|
516
|
+
# v0.0.6 — when the predicate folds to a `Type::Constant` whose
|
|
517
|
+
# value is Ruby-truthy (resp. Ruby-falsey), the unreachable
|
|
518
|
+
# branch is elided so the if-expression's type is the live
|
|
519
|
+
# branch alone. Statement-level branch elision lives in
|
|
520
|
+
# `StatementEvaluator#eval_if`; this handler covers the
|
|
521
|
+
# expression-position ternary form (`a ? b : c`) and any
|
|
522
|
+
# `if`/`unless` reached through `type_of`.
|
|
503
523
|
def type_of_if(node)
|
|
504
524
|
then_type = statements_or_nil(node.statements)
|
|
505
|
-
else_type =
|
|
506
|
-
|
|
507
|
-
type_of(node.subsequent)
|
|
508
|
-
else
|
|
509
|
-
Type::Combinator.constant_of(nil)
|
|
510
|
-
end
|
|
511
|
-
Type::Combinator.union(then_type, else_type)
|
|
525
|
+
else_type = if_else_type(node.subsequent)
|
|
526
|
+
elide_or_union(node.predicate, then_type, else_type)
|
|
512
527
|
end
|
|
513
528
|
|
|
514
529
|
# `unless c; t; else; e; end`. Prism uses `else_clause` here (no
|
|
515
|
-
# `elsif` chain).
|
|
530
|
+
# `elsif` chain). Branch-elision logic mirrors `type_of_if`,
|
|
531
|
+
# inverted: a truthy predicate selects the else branch.
|
|
516
532
|
def type_of_unless(node)
|
|
517
533
|
then_type = statements_or_nil(node.statements)
|
|
518
|
-
else_type =
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
534
|
+
else_type = if_else_type(node.else_clause)
|
|
535
|
+
elide_or_union(node.predicate, else_type, then_type)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def if_else_type(subsequent)
|
|
539
|
+
return Type::Combinator.constant_of(nil) if subsequent.nil?
|
|
540
|
+
|
|
541
|
+
type_of(subsequent)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# Routes the predicate's typed value through branch elision.
|
|
545
|
+
# `live_when_truthy` and `live_when_falsey` are the branch
|
|
546
|
+
# types selected by the predicate's polarity; the names
|
|
547
|
+
# match `IfNode` semantics directly and invert at the
|
|
548
|
+
# `type_of_unless` call site.
|
|
549
|
+
def elide_or_union(predicate, live_when_truthy, live_when_falsey)
|
|
550
|
+
case constant_predicate_polarity(predicate)
|
|
551
|
+
when :truthy then live_when_truthy
|
|
552
|
+
when :falsey then live_when_falsey
|
|
553
|
+
else Type::Combinator.union(live_when_truthy, live_when_falsey)
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# Returns `:truthy`, `:falsey`, or `nil` for an arbitrary
|
|
558
|
+
# predicate expression. Only `Type::Constant` answers
|
|
559
|
+
# decisively — `Union[true, false]`, `Nominal[bool]`, and
|
|
560
|
+
# `Dynamic[T]` keep both branches live.
|
|
561
|
+
def constant_predicate_polarity(predicate)
|
|
562
|
+
return nil if predicate.nil?
|
|
563
|
+
|
|
564
|
+
type = type_of(predicate)
|
|
565
|
+
return nil unless type.is_a?(Type::Constant)
|
|
566
|
+
|
|
567
|
+
type.value ? :truthy : :falsey
|
|
525
568
|
end
|
|
526
569
|
|
|
527
570
|
def type_of_else(node)
|
|
528
571
|
statements_or_nil(node.statements)
|
|
529
572
|
end
|
|
530
573
|
|
|
531
|
-
# `a && b` and `a || b` short-circuit
|
|
532
|
-
#
|
|
533
|
-
#
|
|
574
|
+
# `a && b` and `a || b` short-circuit at the value level:
|
|
575
|
+
# `a && b` returns `a` when `a` is falsey, else `b`.
|
|
576
|
+
# `a || b` returns `a` when `a` is truthy, else `b`.
|
|
577
|
+
#
|
|
578
|
+
# v0.0.6 — when the left operand folds to a `Type::Constant`,
|
|
579
|
+
# we know which side actually flows through, so the result
|
|
580
|
+
# is one operand's type instead of a union. Otherwise the
|
|
581
|
+
# union-of-both-operands fallback is preserved.
|
|
534
582
|
def type_of_and_or(node)
|
|
535
|
-
|
|
583
|
+
left_type = type_of(node.left)
|
|
584
|
+
polarity = constant_value_polarity(left_type)
|
|
585
|
+
return short_circuit_for(node, left_type, polarity) if polarity
|
|
586
|
+
|
|
587
|
+
Type::Combinator.union(left_type, type_of(node.right))
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def short_circuit_for(node, left_type, polarity)
|
|
591
|
+
and_node = node.is_a?(Prism::AndNode)
|
|
592
|
+
if polarity == :truthy
|
|
593
|
+
and_node ? type_of(node.right) : left_type
|
|
594
|
+
else
|
|
595
|
+
and_node ? left_type : type_of(node.right)
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# Returns `:truthy` / `:falsey` for a `Type::Constant`,
|
|
600
|
+
# nil otherwise. Mirrors `constant_predicate_polarity` but
|
|
601
|
+
# operates on a typed value (already-type-of'd) rather
|
|
602
|
+
# than a Prism node, so the same predicate analysis can
|
|
603
|
+
# be reused in both contexts.
|
|
604
|
+
def constant_value_polarity(type)
|
|
605
|
+
return nil unless type.is_a?(Type::Constant)
|
|
606
|
+
|
|
607
|
+
type.value ? :truthy : :falsey
|
|
536
608
|
end
|
|
537
609
|
|
|
538
610
|
def type_of_case(node)
|
|
@@ -631,7 +703,18 @@ module Rigor
|
|
|
631
703
|
Type::Combinator.constant_of(Range.new(left, right, node.exclude_end?))
|
|
632
704
|
end
|
|
633
705
|
|
|
634
|
-
|
|
706
|
+
# v0.0.7 — non-interpolated regex literals lift to
|
|
707
|
+
# `Constant<Regexp>` so `Constant<String>#scan(/regex/)`
|
|
708
|
+
# / `#match(/regex/)` etc. can fold through the catalog
|
|
709
|
+
# tier. Interpolated regexes (`/foo#{x}/`) reach the
|
|
710
|
+
# second `Prism::InterpolatedRegularExpressionNode` arm
|
|
711
|
+
# which keeps the conservative `Nominal[Regexp]` answer.
|
|
712
|
+
def type_of_regexp(node)
|
|
713
|
+
return Type::Combinator.nominal_of(Regexp) unless node.is_a?(Prism::RegularExpressionNode)
|
|
714
|
+
|
|
715
|
+
regex = Regexp.new(node.unescaped, node.options)
|
|
716
|
+
Type::Combinator.constant_of(regex)
|
|
717
|
+
rescue StandardError
|
|
635
718
|
Type::Combinator.nominal_of(Regexp)
|
|
636
719
|
end
|
|
637
720
|
|
|
@@ -701,12 +784,17 @@ module Rigor
|
|
|
701
784
|
# when every element is a non-splat value. Splatted entries
|
|
702
785
|
# (`[*xs, 1]`) preserve the Slice 4 phase 2d behavior: we union
|
|
703
786
|
# the contributed element types and emit
|
|
704
|
-
# `Nominal[Array, [union]]`.
|
|
705
|
-
#
|
|
706
|
-
#
|
|
787
|
+
# `Nominal[Array, [union]]`.
|
|
788
|
+
#
|
|
789
|
+
# v0.0.6 — the empty literal `[]` resolves to the empty
|
|
790
|
+
# `Tuple[]` carrier rather than the raw `Nominal[Array]`.
|
|
791
|
+
# Both carriers erase to RBS `Array`, but `Tuple[]` pins
|
|
792
|
+
# the literal's known arity (zero), which lets the
|
|
793
|
+
# per-element block fold concatenate across all-empty
|
|
794
|
+
# positions like `[1, 2].flat_map { |_| [] }`.
|
|
707
795
|
def array_type_for(node)
|
|
708
796
|
elements = node.elements
|
|
709
|
-
return Type::Combinator.
|
|
797
|
+
return Type::Combinator.tuple_of if elements.empty?
|
|
710
798
|
|
|
711
799
|
if elements.any?(Prism::SplatNode)
|
|
712
800
|
element_types = elements.map { |e| type_of(e) }
|
|
@@ -739,6 +827,7 @@ module Rigor
|
|
|
739
827
|
# for the CallNode itself (the inner type_of calls already record
|
|
740
828
|
# their own fallbacks for unrecognised receivers/args, so the tracer
|
|
741
829
|
# captures both the immediate dispatch miss and the deeper cause).
|
|
830
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
742
831
|
def call_type_for(node)
|
|
743
832
|
receiver = call_receiver_type_for(node)
|
|
744
833
|
arg_types = call_arg_types(node)
|
|
@@ -771,6 +860,18 @@ module Rigor
|
|
|
771
860
|
return dynamic_top
|
|
772
861
|
end
|
|
773
862
|
|
|
863
|
+
# v0.0.6 phase 2 — per-element block fold for Tuple
|
|
864
|
+
# receivers. When `[a, b, c].map { |x| f(x) }` and the
|
|
865
|
+
# receiver is a `Tuple` carrier with finite elements,
|
|
866
|
+
# type the block body once per position with the
|
|
867
|
+
# corresponding element bound to the block parameter
|
|
868
|
+
# and assemble the results into a `Tuple[U_1..U_n]`.
|
|
869
|
+
# This sits ahead of `MethodDispatcher.dispatch` so
|
|
870
|
+
# the RBS tier does not re-widen the answer back to
|
|
871
|
+
# `Array[union]`.
|
|
872
|
+
per_element = try_per_element_block_fold(node, receiver)
|
|
873
|
+
return per_element if per_element
|
|
874
|
+
|
|
774
875
|
result = MethodDispatcher.dispatch(
|
|
775
876
|
receiver_type: receiver,
|
|
776
877
|
method_name: node.name,
|
|
@@ -797,6 +898,7 @@ module Rigor
|
|
|
797
898
|
|
|
798
899
|
fallback_for(node, family: :prism)
|
|
799
900
|
end
|
|
901
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
800
902
|
|
|
801
903
|
# v0.0.2 #5 — re-types the body of a user-defined
|
|
802
904
|
# instance method with the call site's argument types
|
|
@@ -976,6 +1078,189 @@ module Rigor
|
|
|
976
1078
|
|
|
977
1079
|
block_scope.type_of(body)
|
|
978
1080
|
end
|
|
1081
|
+
|
|
1082
|
+
# v0.0.6 phase 2 — per-element block fold for Tuple
|
|
1083
|
+
# receivers under `:map` / `:collect`. Walks every Tuple
|
|
1084
|
+
# position, binds the block parameter to that element's
|
|
1085
|
+
# type, and re-types the block body. The per-position
|
|
1086
|
+
# results are assembled into `Tuple[U_1..U_n]`, strictly
|
|
1087
|
+
# tighter than the RBS-projected `Array[union]`.
|
|
1088
|
+
#
|
|
1089
|
+
# Declines (returns nil) when the receiver is not a
|
|
1090
|
+
# `Tuple` with at least one element, when the call has
|
|
1091
|
+
# no `Prism::BlockNode`, when the method is outside the
|
|
1092
|
+
# supported set, when block typing raises mid-loop, or
|
|
1093
|
+
# when the block has no body. The decline path leaves
|
|
1094
|
+
# the dispatch chain untouched.
|
|
1095
|
+
PER_ELEMENT_TUPLE_METHODS = Set[
|
|
1096
|
+
:map, :collect, :filter_map, :flat_map,
|
|
1097
|
+
:find, :detect, :find_index, :index
|
|
1098
|
+
].freeze
|
|
1099
|
+
private_constant :PER_ELEMENT_TUPLE_METHODS
|
|
1100
|
+
|
|
1101
|
+
# Cardinality cap for per-element block fold over
|
|
1102
|
+
# finite-bound `Constant<Range>` receivers. Walking
|
|
1103
|
+
# `(1..1_000_000).map { … }` element-wise would balloon
|
|
1104
|
+
# block-typing cost and explode the resulting Tuple, so
|
|
1105
|
+
# only short ranges expand into per-position folds.
|
|
1106
|
+
# Larger ranges decline so the RBS tier widens.
|
|
1107
|
+
PER_ELEMENT_RANGE_LIMIT = 8
|
|
1108
|
+
private_constant :PER_ELEMENT_RANGE_LIMIT
|
|
1109
|
+
|
|
1110
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1111
|
+
def try_per_element_block_fold(call_node, receiver_type)
|
|
1112
|
+
return nil unless PER_ELEMENT_TUPLE_METHODS.include?(call_node.name)
|
|
1113
|
+
return nil if find_family_with_args?(call_node)
|
|
1114
|
+
|
|
1115
|
+
element_types = per_element_elements_of(receiver_type)
|
|
1116
|
+
return nil if element_types.nil? || element_types.empty?
|
|
1117
|
+
|
|
1118
|
+
block_node = call_node.block
|
|
1119
|
+
return nil unless block_node.is_a?(Prism::BlockNode)
|
|
1120
|
+
|
|
1121
|
+
per_position = element_types.map do |element_type|
|
|
1122
|
+
type_block_body_with_param(block_node, [element_type])
|
|
1123
|
+
end
|
|
1124
|
+
return nil if per_position.any?(&:nil?)
|
|
1125
|
+
|
|
1126
|
+
assemble_per_element_result(call_node.name, per_position, element_types)
|
|
1127
|
+
end
|
|
1128
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1129
|
+
|
|
1130
|
+
# Returns the per-position element types for a finite,
|
|
1131
|
+
# statically-known receiver shape — or nil when the
|
|
1132
|
+
# receiver does not pin a finite element list.
|
|
1133
|
+
#
|
|
1134
|
+
# `Tuple[A, B, …]` → [A, B, …]
|
|
1135
|
+
# `Constant<a..b>` → [Constant[a], …, Constant[b]]
|
|
1136
|
+
# everything else → nil
|
|
1137
|
+
#
|
|
1138
|
+
# Note: `Type::IntegerRange` is the bounded-Integer
|
|
1139
|
+
# carrier (`int<a, b>` represents "an Integer between
|
|
1140
|
+
# a and b"), not a Range value. Calls like `.map` /
|
|
1141
|
+
# `.find` on an `IntegerRange` receiver would resolve
|
|
1142
|
+
# to `Integer#map` / `Integer#find` — neither exists —
|
|
1143
|
+
# so IntegerRange does NOT participate in this fold.
|
|
1144
|
+
def per_element_elements_of(receiver_type)
|
|
1145
|
+
case receiver_type
|
|
1146
|
+
when Type::Tuple then receiver_type.elements
|
|
1147
|
+
when Type::Constant then constant_range_elements(receiver_type.value)
|
|
1148
|
+
end
|
|
1149
|
+
end
|
|
1150
|
+
|
|
1151
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
1152
|
+
def constant_range_elements(value)
|
|
1153
|
+
return nil unless value.is_a?(Range)
|
|
1154
|
+
return nil unless value.begin.is_a?(Integer) && value.end.is_a?(Integer)
|
|
1155
|
+
|
|
1156
|
+
cardinality = value.exclude_end? ? value.end - value.begin : value.end - value.begin + 1
|
|
1157
|
+
return nil if cardinality <= 0 || cardinality > PER_ELEMENT_RANGE_LIMIT
|
|
1158
|
+
|
|
1159
|
+
value.to_a.map { |v| Type::Combinator.constant_of(v) }
|
|
1160
|
+
end
|
|
1161
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
1162
|
+
|
|
1163
|
+
# `index(value)` and `find_index(value)` carry a positional
|
|
1164
|
+
# argument and search by `==` rather than running the block.
|
|
1165
|
+
# Decline so the RBS tier owns those forms.
|
|
1166
|
+
def find_family_with_args?(call_node)
|
|
1167
|
+
return false unless %i[find_index index].include?(call_node.name)
|
|
1168
|
+
|
|
1169
|
+
args = call_node.arguments
|
|
1170
|
+
!args.nil? && !args.arguments.empty?
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
def assemble_per_element_result(method_name, per_position, element_types)
|
|
1174
|
+
case method_name
|
|
1175
|
+
when :map, :collect then Type::Combinator.tuple_of(*per_position)
|
|
1176
|
+
when :filter_map then assemble_filter_map_result(per_position)
|
|
1177
|
+
when :flat_map then assemble_flat_map_result(per_position)
|
|
1178
|
+
when :find, :detect then assemble_find_result(per_position, element_types)
|
|
1179
|
+
when :find_index, :index then assemble_find_index_result(per_position)
|
|
1180
|
+
end
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
# `filter_map` folds tightly only when every per-position
|
|
1184
|
+
# result is a `Constant`: positions whose value is `nil`
|
|
1185
|
+
# or `false` drop, the rest survive in declaration order.
|
|
1186
|
+
# When any position is non-Constant the dispatcher
|
|
1187
|
+
# declines (returns nil) so the RBS tier widens to
|
|
1188
|
+
# `Array[U]`.
|
|
1189
|
+
def assemble_filter_map_result(per_position)
|
|
1190
|
+
return nil unless per_position.all?(Type::Constant)
|
|
1191
|
+
|
|
1192
|
+
kept = per_position.reject { |type| type.value.nil? || type.value == false }
|
|
1193
|
+
Type::Combinator.tuple_of(*kept)
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
# `flat_map` flattens a single level: if the per-position
|
|
1197
|
+
# result is a `Tuple`, its elements are concatenated; if
|
|
1198
|
+
# it's a non-Array scalar carrier (`Constant<…>` over a
|
|
1199
|
+
# non-Array literal) it contributes one element. We fold
|
|
1200
|
+
# tightly only when every per-position result is one of
|
|
1201
|
+
# those two recognisable shapes — `Nominal[Array[T]]`,
|
|
1202
|
+
# `Union[…]`, and other opaque carriers decline so the
|
|
1203
|
+
# RBS tier widens to `Array[U]`.
|
|
1204
|
+
#
|
|
1205
|
+
# `Type::Constant` only ever holds non-Array scalars (the
|
|
1206
|
+
# carrier rejects Array literals), so a single `Constant`
|
|
1207
|
+
# safely contributes itself as a single Tuple element.
|
|
1208
|
+
def assemble_flat_map_result(per_position)
|
|
1209
|
+
flattened = per_position.flat_map { |type| flat_map_contribution(type) }
|
|
1210
|
+
return nil if flattened.nil? || flattened.any?(&:nil?)
|
|
1211
|
+
|
|
1212
|
+
Type::Combinator.tuple_of(*flattened)
|
|
1213
|
+
end
|
|
1214
|
+
|
|
1215
|
+
def flat_map_contribution(type)
|
|
1216
|
+
case type
|
|
1217
|
+
when Type::Tuple then type.elements
|
|
1218
|
+
when Type::Constant then [type]
|
|
1219
|
+
else [nil]
|
|
1220
|
+
end
|
|
1221
|
+
end
|
|
1222
|
+
|
|
1223
|
+
# `find` / `detect`: returns the first receiver element
|
|
1224
|
+
# whose block result is Ruby-truthy, or `nil` when no
|
|
1225
|
+
# position folds to truthy.
|
|
1226
|
+
#
|
|
1227
|
+
# Folds tightly only when every per-position block result
|
|
1228
|
+
# is a `Type::Constant` — otherwise we cannot decide which
|
|
1229
|
+
# position (if any) is "the first matching one". When the
|
|
1230
|
+
# first decisive truthy position is found, the answer is
|
|
1231
|
+
# the corresponding receiver element. When every position
|
|
1232
|
+
# folds to falsey, the answer is `Constant[nil]`.
|
|
1233
|
+
def assemble_find_result(per_position, element_types)
|
|
1234
|
+
return nil unless per_position.all?(Type::Constant)
|
|
1235
|
+
|
|
1236
|
+
first_truthy_index = per_position.index { |type| truthy_constant?(type) }
|
|
1237
|
+
return Type::Combinator.constant_of(nil) if first_truthy_index.nil?
|
|
1238
|
+
|
|
1239
|
+
element_types[first_truthy_index]
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
# `find_index` / `index`: returns the index of the first
|
|
1243
|
+
# truthy position, or `Constant[nil]` when nothing matches.
|
|
1244
|
+
def assemble_find_index_result(per_position)
|
|
1245
|
+
return nil unless per_position.all?(Type::Constant)
|
|
1246
|
+
|
|
1247
|
+
first_truthy_index = per_position.index { |type| truthy_constant?(type) }
|
|
1248
|
+
return Type::Combinator.constant_of(nil) if first_truthy_index.nil?
|
|
1249
|
+
|
|
1250
|
+
Type::Combinator.constant_of(first_truthy_index)
|
|
1251
|
+
end
|
|
1252
|
+
|
|
1253
|
+
def truthy_constant?(type)
|
|
1254
|
+
type.is_a?(Type::Constant) && type.value && type.value != false
|
|
1255
|
+
end
|
|
1256
|
+
|
|
1257
|
+
def type_block_body_with_param(block_node, expected_param_types)
|
|
1258
|
+
bindings = BlockParameterBinder.new(expected_param_types: expected_param_types).bind(block_node)
|
|
1259
|
+
block_scope = bindings.reduce(scope) { |acc, (name, type)| acc.with_local(name, type) }
|
|
1260
|
+
type_block_body(block_node, block_scope)
|
|
1261
|
+
rescue StandardError
|
|
1262
|
+
nil
|
|
1263
|
+
end
|
|
979
1264
|
end
|
|
980
1265
|
# rubocop:enable Metrics/ClassLength
|
|
981
1266
|
end
|