rigortype 0.0.6 → 0.0.8

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.
@@ -72,6 +72,19 @@ module Rigor
72
72
  size: :tuple_size,
73
73
  length: :tuple_size,
74
74
  count: :tuple_size,
75
+ empty?: :tuple_empty?,
76
+ any?: :tuple_any?,
77
+ all?: :tuple_all?,
78
+ none?: :tuple_none?,
79
+ include?: :tuple_include?,
80
+ sum: :tuple_sum,
81
+ min: :tuple_min,
82
+ max: :tuple_max,
83
+ sort: :tuple_sort,
84
+ reverse: :tuple_reverse,
85
+ to_a: :tuple_to_a,
86
+ to_h: :tuple_to_h,
87
+ zip: :tuple_zip,
75
88
  :[] => :tuple_index,
76
89
  fetch: :tuple_index,
77
90
  dig: :tuple_dig
@@ -80,6 +93,18 @@ module Rigor
80
93
  HASH_SHAPE_HANDLERS = {
81
94
  size: :hash_size,
82
95
  length: :hash_size,
96
+ count: :hash_size,
97
+ empty?: :hash_empty?,
98
+ any?: :hash_any?,
99
+ keys: :hash_keys,
100
+ values: :hash_values,
101
+ first: :hash_first,
102
+ flatten: :hash_flatten,
103
+ compact: :hash_compact,
104
+ to_a: :hash_to_a,
105
+ to_h: :hash_to_h,
106
+ invert: :hash_invert,
107
+ merge: :hash_merge,
83
108
  :[] => :hash_lookup,
84
109
  fetch: :hash_lookup,
85
110
  dig: :hash_dig,
@@ -369,6 +394,239 @@ module Rigor
369
394
  Type::Combinator.constant_of(tuple.elements.size)
370
395
  end
371
396
 
397
+ # `tuple.empty?` — folds to a precise bool from the
398
+ # tuple's known arity.
399
+ # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
400
+ def tuple_empty?(tuple, _method_name, args)
401
+ return nil unless args.empty?
402
+
403
+ Type::Combinator.constant_of(tuple.elements.empty?)
404
+ end
405
+
406
+ # `tuple.any?` (no-arg, no-block) — empty tuple → false,
407
+ # non-empty → true. The block / arg forms flow through
408
+ # `BlockFolding` and the RBS tier.
409
+ def tuple_any?(tuple, _method_name, args)
410
+ return nil unless args.empty?
411
+
412
+ Type::Combinator.constant_of(!tuple.elements.empty?)
413
+ end
414
+
415
+ # `tuple.all?` (no-arg, no-block) — true for empty
416
+ # tuple (vacuous truth) AND for non-empty tuples whose
417
+ # every element is provably truthy. Mixed / unknown
418
+ # element truthiness declines so the RBS / BlockFolding
419
+ # tiers can still answer.
420
+ def tuple_all?(tuple, _method_name, args)
421
+ return nil unless args.empty?
422
+ return Type::Combinator.constant_of(true) if tuple.elements.empty?
423
+
424
+ decision = tuple_predicate_truthiness(tuple, all: true)
425
+ return nil if decision.nil?
426
+
427
+ Type::Combinator.constant_of(decision)
428
+ end
429
+
430
+ # `tuple.none?` (no-arg, no-block) — true when every
431
+ # element is provably falsey, false when any element is
432
+ # provably truthy. Empty tuple folds to true (vacuous).
433
+ def tuple_none?(tuple, _method_name, args)
434
+ return nil unless args.empty?
435
+ return Type::Combinator.constant_of(true) if tuple.elements.empty?
436
+
437
+ decision = tuple_predicate_truthiness(tuple, all: false)
438
+ return nil if decision.nil?
439
+
440
+ Type::Combinator.constant_of(decision)
441
+ end
442
+
443
+ # `tuple.include?(needle)` — folds to a precise bool when
444
+ # the needle is a `Constant` and the tuple's elements
445
+ # are all `Constant` (so disjointness is checkable).
446
+ # If any element matches the needle's value the answer
447
+ # is `Constant[true]`; if every element is a Constant
448
+ # whose value is structurally distinct from the needle
449
+ # the answer is `Constant[false]`.
450
+ def tuple_include?(tuple, _method_name, args)
451
+ return nil unless args.size == 1
452
+
453
+ needle = args.first
454
+ return nil unless needle.is_a?(Type::Constant)
455
+ return Type::Combinator.constant_of(false) if tuple.elements.empty?
456
+
457
+ return Type::Combinator.constant_of(true) if any_element_matches?(tuple.elements, needle.value)
458
+ return Type::Combinator.constant_of(false) if tuple.elements.all?(Type::Constant)
459
+
460
+ nil
461
+ end
462
+ # rubocop:enable Style/ReturnNilInPredicateMethodDefinition
463
+
464
+ # `tuple.sum` — when every element is a numeric Constant,
465
+ # fold to `Constant[sum]`. Mixed / non-numeric elements
466
+ # decline so RBS widens.
467
+ def tuple_sum(tuple, _method_name, args)
468
+ return nil unless args.empty?
469
+ return Type::Combinator.constant_of(0) if tuple.elements.empty?
470
+
471
+ values = constant_numeric_values(tuple.elements)
472
+ return nil if values.nil?
473
+
474
+ Type::Combinator.constant_of(values.sum)
475
+ end
476
+
477
+ # `tuple.min` / `tuple.max` — fold when every element is
478
+ # a `Constant` whose values share a Ruby-comparable
479
+ # domain. Empty tuples fold to `Constant[nil]`.
480
+ def tuple_min(tuple, _method_name, args)
481
+ tuple_minmax(tuple, args, :min)
482
+ end
483
+
484
+ def tuple_max(tuple, _method_name, args)
485
+ tuple_minmax(tuple, args, :max)
486
+ end
487
+
488
+ def tuple_minmax(tuple, args, edge)
489
+ return nil unless args.empty?
490
+ return Type::Combinator.constant_of(nil) if tuple.elements.empty?
491
+
492
+ values = constant_values(tuple.elements)
493
+ return nil if values.nil?
494
+
495
+ result = values.public_send(edge)
496
+ Type::Combinator.constant_of(result)
497
+ rescue StandardError
498
+ nil
499
+ end
500
+
501
+ # `tuple.sort` — every element must be a `Constant` and
502
+ # the values must Ruby-compare. The result is a Tuple
503
+ # with the same elements in sorted order. Comparison
504
+ # failures (mixed-class incomparable values) decline.
505
+ def tuple_sort(tuple, _method_name, args)
506
+ return nil unless args.empty?
507
+ return tuple if tuple.elements.size <= 1
508
+
509
+ values = constant_values(tuple.elements)
510
+ return nil if values.nil?
511
+
512
+ sorted = values.sort
513
+ Type::Combinator.tuple_of(*sorted.map { |v| Type::Combinator.constant_of(v) })
514
+ rescue StandardError
515
+ nil
516
+ end
517
+
518
+ # `tuple.reverse` — independent of element shape; a
519
+ # tuple-precise reversed Tuple.
520
+ def tuple_reverse(tuple, _method_name, args)
521
+ return nil unless args.empty?
522
+
523
+ Type::Combinator.tuple_of(*tuple.elements.reverse)
524
+ end
525
+
526
+ # `tuple.to_a` — Tuple is structurally identical to its
527
+ # to_a (Ruby returns the receiver itself for an Array).
528
+ def tuple_to_a(tuple, _method_name, args)
529
+ return nil unless args.empty?
530
+
531
+ tuple
532
+ end
533
+
534
+ # `tuple.zip(other_1, other_2, …)` — pairs the receiver's
535
+ # per-position elements with the per-position elements of
536
+ # each other Tuple-shaped argument. The result is a Tuple
537
+ # of Tuples whose arity matches the receiver: position
538
+ # `i` is `Tuple[receiver[i], other_1[i], other_2[i], …]`.
539
+ # Out-of-range positions in any `other_k` contribute
540
+ # `Constant[nil]` (matching Ruby's runtime semantics).
541
+ # Declines when any `other_k` is not a Tuple, since the
542
+ # arity is then unknown and the result would be
543
+ # `Array[Array[…]]` — RBS already gives that answer.
544
+ def tuple_zip(tuple, _method_name, args)
545
+ return nil if args.empty? || args.size > MAX_ZIP_ARITY
546
+ return nil unless args.all?(Type::Tuple)
547
+
548
+ zipped = tuple.elements.each_with_index.map do |elem, i|
549
+ positions = [elem] + args.map { |other| other.elements[i] || Type::Combinator.constant_of(nil) }
550
+ Type::Combinator.tuple_of(*positions)
551
+ end
552
+ Type::Combinator.tuple_of(*zipped)
553
+ end
554
+
555
+ MAX_ZIP_ARITY = 8
556
+ private_constant :MAX_ZIP_ARITY
557
+
558
+ # `tuple.to_h` — folds when every Tuple element is itself
559
+ # a 2-element Tuple whose first element is a `Constant`
560
+ # (so it can serve as a Hash key). Produces a closed
561
+ # `HashShape` whose entries mirror the per-position
562
+ # pairs. Empty Tuples fold to the empty HashShape.
563
+ # rubocop:disable Metrics/CyclomaticComplexity
564
+ def tuple_to_h(tuple, _method_name, args)
565
+ return nil unless args.empty?
566
+ return Type::Combinator.hash_shape_of({}) if tuple.elements.empty?
567
+
568
+ pairs = tuple.elements.map { |e| tuple_to_h_pair(e) }
569
+ return nil if pairs.any?(&:nil?)
570
+ return nil unless pairs.map(&:first).uniq.size == pairs.size
571
+
572
+ Type::Combinator.hash_shape_of(pairs.to_h)
573
+ end
574
+ # rubocop:enable Metrics/CyclomaticComplexity
575
+
576
+ def tuple_to_h_pair(element)
577
+ return nil unless element.is_a?(Type::Tuple)
578
+ return nil unless element.elements.size == 2
579
+
580
+ key = element.elements[0]
581
+ value = element.elements[1]
582
+ return nil unless key.is_a?(Type::Constant)
583
+
584
+ [key.value, value]
585
+ end
586
+
587
+ # Returns `true` / `false` if every element's truthiness
588
+ # agrees, nil for mixed-or-unknown shapes. `all: true`
589
+ # checks every element is truthy; `all: false` checks
590
+ # every element is falsey.
591
+ def tuple_predicate_truthiness(tuple, all:)
592
+ samples = tuple.elements.map { |e| element_truthiness(e) }
593
+ return nil if samples.any?(:unknown)
594
+
595
+ if all
596
+ samples.all?(:truthy)
597
+ else
598
+ samples.all?(:falsey)
599
+ end
600
+ end
601
+
602
+ def element_truthiness(type)
603
+ return :unknown unless type.is_a?(Type::Constant)
604
+
605
+ falsey = type.value.nil? || type.value == false
606
+ falsey ? :falsey : :truthy
607
+ end
608
+
609
+ def any_element_matches?(elements, value)
610
+ elements.any? { |e| e.is_a?(Type::Constant) && e.value == value }
611
+ end
612
+
613
+ # Per-element Constant value extraction. Returns nil
614
+ # when any element is non-Constant, so the caller can
615
+ # decline.
616
+ def constant_values(elements)
617
+ return nil unless elements.all?(Type::Constant)
618
+
619
+ elements.map(&:value)
620
+ end
621
+
622
+ def constant_numeric_values(elements)
623
+ values = constant_values(elements)
624
+ return nil if values.nil?
625
+ return nil unless values.all?(Numeric)
626
+
627
+ values
628
+ end
629
+
372
630
  # `tuple[i]`, `tuple[range]`, `tuple[start, length]`, and
373
631
  # `tuple.fetch(i)` for static arguments. Out-of-range single
374
632
  # indices still fall through because the same handler serves
@@ -463,6 +721,157 @@ module Rigor
463
721
  Type::Combinator.constant_of(shape.pairs.size)
464
722
  end
465
723
 
724
+ # `shape.empty?` — folds to a precise bool when the
725
+ # shape's emptiness is statically known. Closed shapes
726
+ # with no optional keys have a fixed size, so empty?
727
+ # is `Constant[shape.pairs.empty?]`. The handler returns
728
+ # `Type::t | nil` (nil signals "no rule, defer to next
729
+ # tier") so the standard predicate-return rubocop rule
730
+ # does not apply.
731
+ # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
732
+ def hash_empty?(shape, _method_name, args)
733
+ return nil unless args.empty?
734
+ return nil unless shape.closed?
735
+ return nil unless shape.optional_keys.empty?
736
+
737
+ Type::Combinator.constant_of(shape.pairs.empty?)
738
+ end
739
+
740
+ # `shape.any?` (no block, no arg) — opposite of
741
+ # `empty?`. The block / arg forms are answered by the
742
+ # RBS / BlockFolding tier.
743
+ def hash_any?(shape, _method_name, args)
744
+ return nil unless args.empty?
745
+ return nil unless shape.closed?
746
+ return nil unless shape.optional_keys.empty?
747
+
748
+ Type::Combinator.constant_of(!shape.pairs.empty?)
749
+ end
750
+ # rubocop:enable Style/ReturnNilInPredicateMethodDefinition
751
+
752
+ # `shape.keys` — returns a `Tuple[Constant<k>…]` for a
753
+ # closed shape with no optional keys; the Tuple's
754
+ # arity matches the shape's per-key declaration order
755
+ # so downstream `tuple[i]` projections stay precise.
756
+ def hash_keys(shape, _method_name, args)
757
+ return nil unless args.empty?
758
+ return nil unless shape.closed?
759
+ return nil unless shape.optional_keys.empty?
760
+
761
+ Type::Combinator.tuple_of(*shape.pairs.keys.map { |k| Type::Combinator.constant_of(k) })
762
+ end
763
+
764
+ # `shape.values` — returns a `Tuple[V_1, …, V_n]` for a
765
+ # closed shape with no optional keys, the Tuple's arity
766
+ # matching the shape's per-key value order.
767
+ def hash_values(shape, _method_name, args)
768
+ return nil unless args.empty?
769
+ return nil unless shape.closed?
770
+ return nil unless shape.optional_keys.empty?
771
+
772
+ Type::Combinator.tuple_of(*shape.pairs.values)
773
+ end
774
+
775
+ # `shape.to_a` — returns a per-entry `Tuple[Tuple[K, V], …]`
776
+ # for a closed shape with no optional keys.
777
+ def hash_to_a(shape, _method_name, args)
778
+ return nil unless args.empty?
779
+ return nil unless shape.closed?
780
+ return nil unless shape.optional_keys.empty?
781
+
782
+ entries = shape.pairs.map do |k, v|
783
+ Type::Combinator.tuple_of(Type::Combinator.constant_of(k), v)
784
+ end
785
+ Type::Combinator.tuple_of(*entries)
786
+ end
787
+
788
+ # `shape.to_h` — Hash is structurally identical to its
789
+ # to_h (Ruby returns the receiver itself for a Hash).
790
+ def hash_to_h(shape, _method_name, args)
791
+ return nil unless args.empty?
792
+
793
+ shape
794
+ end
795
+
796
+ # `shape.invert` — swaps keys and values. Folds when
797
+ # every value is a `Constant` whose value is a Symbol
798
+ # or String (the only hashable types that
799
+ # `HashShape` accepts as keys). Duplicate values would
800
+ # alias under inversion, so Rigor declines on
801
+ # collisions rather than silently dropping entries.
802
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
803
+ def hash_invert(shape, _method_name, args)
804
+ return nil unless args.empty?
805
+ return nil unless shape.closed?
806
+ return nil unless shape.optional_keys.empty?
807
+ return nil unless shape.pairs.values.all?(Type::Constant)
808
+ return nil unless shape.pairs.values.all? { |v| v.value.is_a?(Symbol) || v.value.is_a?(String) }
809
+
810
+ inverted = shape.pairs.each_with_object({}) do |(k, v), acc|
811
+ return nil if acc.key?(v.value)
812
+
813
+ acc[v.value] = Type::Combinator.constant_of(k)
814
+ end
815
+ Type::Combinator.hash_shape_of(inverted)
816
+ end
817
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
818
+
819
+ # `shape.first` — returns the first `[k, v]` pair as a
820
+ # 2-Tuple, or `Constant[nil]` when the shape is empty.
821
+ # Folds only on closed shapes with no optional keys
822
+ # (open shapes might contribute extra keys at runtime).
823
+ def hash_first(shape, _method_name, args)
824
+ return nil unless args.empty?
825
+ return nil unless shape.closed?
826
+ return nil unless shape.optional_keys.empty?
827
+ return Type::Combinator.constant_of(nil) if shape.pairs.empty?
828
+
829
+ key, value = shape.pairs.first
830
+ Type::Combinator.tuple_of(Type::Combinator.constant_of(key), value)
831
+ end
832
+
833
+ # `shape.flatten` — flattens to `[k_1, v_1, k_2, v_2, …]`
834
+ # at depth 1. Closed shapes only; element order matches
835
+ # the per-key declaration order.
836
+ def hash_flatten(shape, _method_name, args)
837
+ return nil unless args.empty?
838
+ return nil unless shape.closed?
839
+ return nil unless shape.optional_keys.empty?
840
+
841
+ elements = shape.pairs.flat_map { |k, v| [Type::Combinator.constant_of(k), v] }
842
+ Type::Combinator.tuple_of(*elements)
843
+ end
844
+
845
+ # `shape.compact` — drops every entry whose value is
846
+ # `Constant[nil]`. Folds only when every value is a
847
+ # `Constant` (so the drop set is decidable). Mixed-shape
848
+ # values decline so the RBS tier widens.
849
+ def hash_compact(shape, _method_name, args)
850
+ return nil unless args.empty?
851
+ return nil unless shape.closed?
852
+ return nil unless shape.optional_keys.empty?
853
+ return nil unless shape.pairs.values.all?(Type::Constant)
854
+
855
+ kept = shape.pairs.reject { |_k, v| v.value.nil? }
856
+ Type::Combinator.hash_shape_of(kept)
857
+ end
858
+
859
+ # `shape.merge(other)` — when both sides are closed
860
+ # HashShape with no optional keys, fold to the merged
861
+ # HashShape. Right-hand entries override left-hand
862
+ # entries on key collision (matching Ruby's runtime
863
+ # `Hash#merge`).
864
+ def hash_merge(shape, _method_name, args)
865
+ return nil unless args.size == 1
866
+ return nil unless shape.closed? && shape.optional_keys.empty?
867
+
868
+ other = args.first
869
+ return nil unless other.is_a?(Type::HashShape)
870
+ return nil unless other.closed? && other.optional_keys.empty?
871
+
872
+ Type::Combinator.hash_shape_of(shape.pairs.merge(other.pairs))
873
+ end
874
+
466
875
  # `shape[k]` and `shape.fetch(k)` for a static symbol/string
467
876
  # key. Missing-key resolution depends on the method:
468
877
  #
@@ -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 "method_dispatcher/constant_folding"
5
6
  require_relative "method_dispatcher/shape_dispatch"
@@ -41,7 +42,7 @@ module Rigor
41
42
  # The dispatcher's public signature reserves space for `block_type:`
42
43
  # and ADR-2 plugin extensions (later slices), so call sites added
43
44
  # now do not have to be rewritten when those tiers arrive.
44
- module MethodDispatcher
45
+ module MethodDispatcher # rubocop:disable Metrics/ModuleLength
45
46
  module_function
46
47
 
47
48
  # @param receiver_type [Rigor::Type, nil] type of the receiver expression, or
@@ -96,7 +97,7 @@ module Rigor
96
97
  # arity-based fold tiers above it filter out the common
97
98
  # cases first. When `block_type` is nil the tier is a no-op.
98
99
  def dispatch_precise_tiers(receiver_type, method_name, arg_types, block_type = nil)
99
- meta_result = try_meta_introspection(receiver_type, method_name)
100
+ meta_result = try_meta_introspection(receiver_type, method_name, arg_types)
100
101
  return meta_result if meta_result
101
102
 
102
103
  ConstantFolding.try_fold(receiver: receiver_type, method_name: method_name, args: arg_types) ||
@@ -124,16 +125,13 @@ module Rigor
124
125
  end
125
126
 
126
127
  def user_class_fallback_receiver(receiver_type, environment)
127
- loader = environment.rbs_loader
128
- return nil if loader.nil?
129
-
130
128
  case receiver_type
131
129
  when Type::Nominal
132
- return nil if loader.class_known?(receiver_type.class_name)
130
+ return nil if Rigor::Reflection.rbs_class_known?(receiver_type.class_name, environment: environment)
133
131
 
134
132
  environment.nominal_for_name("Object")
135
133
  when Type::Singleton
136
- return nil if loader.class_known?(receiver_type.class_name)
134
+ return nil if Rigor::Reflection.rbs_class_known?(receiver_type.class_name, environment: environment)
137
135
 
138
136
  environment.singleton_for_name("Class")
139
137
  end
@@ -151,10 +149,10 @@ module Rigor
151
149
  # adjacent calls and the trivial `instance_of?(self)`
152
150
  # later as the rule catalogue grows; for now only `class`
153
151
  # is handled.
154
- def try_meta_introspection(receiver_type, method_name)
152
+ def try_meta_introspection(receiver_type, method_name, arg_types = [])
155
153
  case method_name
156
154
  when :class then meta_class(receiver_type)
157
- when :new then meta_new(receiver_type)
155
+ when :new then meta_new(receiver_type, arg_types)
158
156
  end
159
157
  end
160
158
 
@@ -171,12 +169,74 @@ module Rigor
171
169
  # plumbing for user classes, so a discovered-class
172
170
  # `ScanAccumulator.new` types as `Nominal[ScanAccumulator]`
173
171
  # rather than `Class`.
174
- def meta_new(receiver_type)
172
+ #
173
+ # v0.0.7 — for the curated set of immutable scalar-shaped
174
+ # classes that `Type::Constant::SCALAR_CLASSES` accepts
175
+ # (today: `Pathname`), `.new(Constant<…>)` lifts to a
176
+ # `Constant<…>` carrier so downstream method calls fold
177
+ # through the standard catalog tier.
178
+ def meta_new(receiver_type, arg_types = [])
175
179
  return nil unless receiver_type.is_a?(Type::Singleton)
176
180
 
181
+ constant_lift = constant_constructor_lift(receiver_type.class_name, arg_types)
182
+ return constant_lift if constant_lift
183
+
184
+ array_lift = array_new_lift(receiver_type.class_name, arg_types)
185
+ return array_lift if array_lift
186
+
177
187
  Type::Combinator.nominal_of(receiver_type.class_name)
178
188
  end
179
189
 
190
+ CONSTANT_CONSTRUCTORS = {
191
+ "Pathname" => ->(arg) { Pathname.new(arg) }
192
+ }.freeze
193
+ private_constant :CONSTANT_CONSTRUCTORS
194
+
195
+ def constant_constructor_lift(class_name, arg_types)
196
+ builder = CONSTANT_CONSTRUCTORS[class_name]
197
+ return nil if builder.nil?
198
+ return nil unless arg_types.size == 1
199
+
200
+ arg = arg_types.first
201
+ return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(String)
202
+
203
+ result = builder.call(arg.value)
204
+ Type::Combinator.constant_of(result)
205
+ rescue StandardError
206
+ nil
207
+ end
208
+
209
+ # `Array.new(n, value)` and `Array.new(n)` (no value, default
210
+ # `nil`) lift to a per-position `Tuple[…]` when `n` is a
211
+ # small `Constant<Integer>`. Cap at `ARRAY_NEW_TUPLE_LIMIT`
212
+ # (16) so a `Array.new(1_000_000)` does not balloon the
213
+ # carrier; oversize calls fall back to `Nominal[Array]`.
214
+ ARRAY_NEW_TUPLE_LIMIT = 16
215
+ private_constant :ARRAY_NEW_TUPLE_LIMIT
216
+
217
+ def array_new_lift(class_name, arg_types)
218
+ return nil unless class_name == "Array"
219
+ return nil if arg_types.empty? || arg_types.size > 2
220
+
221
+ size = array_new_size(arg_types.first)
222
+ return nil if size.nil? || size.negative? || size > ARRAY_NEW_TUPLE_LIMIT
223
+
224
+ fill = array_new_fill(arg_types[1])
225
+ Type::Combinator.tuple_of(*Array.new(size, fill))
226
+ end
227
+
228
+ def array_new_size(type)
229
+ return nil unless type.is_a?(Type::Constant) && type.value.is_a?(Integer)
230
+
231
+ type.value
232
+ end
233
+
234
+ def array_new_fill(type)
235
+ return Type::Combinator.constant_of(nil) if type.nil?
236
+
237
+ type
238
+ end
239
+
180
240
  CONSTANT_METACLASSES = {
181
241
  Integer => "Integer", Float => "Float", String => "String",
182
242
  Symbol => "Symbol", Range => "Range",
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "prism"
4
4
 
5
+ require_relative "../reflection"
5
6
  require_relative "../type"
6
7
  require_relative "../rbs_extended"
7
8
  require_relative "rbs_type_translator"
@@ -136,18 +137,15 @@ module Rigor
136
137
  def lookup_rbs_method(def_node)
137
138
  return nil if @class_path.nil?
138
139
 
139
- loader = @environment.rbs_loader
140
- return nil if loader.nil?
141
-
142
140
  method_name = def_node.name
143
141
  # `def self.foo` always means a singleton method on the
144
142
  # immediate enclosing class. `def foo` inside `class << self`
145
143
  # is also a singleton method (the StatementEvaluator threads
146
144
  # the `singleton:` flag through this case).
147
145
  if def_node.receiver.is_a?(Prism::SelfNode) || @singleton
148
- loader.singleton_method(class_name: @class_path, method_name: method_name)
146
+ Rigor::Reflection.singleton_method_definition(@class_path, method_name, environment: @environment)
149
147
  else
150
- loader.instance_method(class_name: @class_path, method_name: method_name)
148
+ Rigor::Reflection.instance_method_definition(@class_path, method_name, environment: @environment)
151
149
  end
152
150
  end
153
151
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "prism"
4
4
 
5
+ require_relative "../reflection"
5
6
  require_relative "../type"
6
7
  require_relative "../environment"
7
8
  require_relative "../rbs_extended"
@@ -285,6 +286,8 @@ module Rigor
285
286
  complement_integer_range(current_type, refinement_type)
286
287
  when Type::Intersection
287
288
  complement_intersection(current_type, refinement_type)
289
+ when Type::Refined
290
+ complement_refined(current_type, refinement_type)
288
291
  else
289
292
  current_type
290
293
  end
@@ -526,6 +529,38 @@ module Rigor
526
529
  Type::Combinator.union(*per_member)
527
530
  end
528
531
 
532
+ # v0.0.7 — complement of a `Refined[base, predicate]`
533
+ # refinement within `current_type`. Walks the current
534
+ # type's union members; parts that are disjoint from
535
+ # the refinement's base survive unchanged; parts equal
536
+ # to the refinement itself drop entirely (they were
537
+ # exactly the negated subset); parts that overlap the
538
+ # base contribute `Difference[part, refined]` so
539
+ # downstream narrowing knows the refinement subset is
540
+ # excluded.
541
+ #
542
+ # The result is sound but imprecise: without a
543
+ # complementary carrier (e.g. `mixed-case-string` for
544
+ # `~lowercase-string`) we cannot enumerate the surviving
545
+ # values. Difference is the carrier-of-last-resort, and
546
+ # the existing `Type::Difference` consumers already
547
+ # treat it correctly.
548
+ def complement_refined(current_type, refined)
549
+ base = refined.base
550
+ parts = current_type.is_a?(Type::Union) ? current_type.members : [current_type]
551
+
552
+ survivors = parts.map do |part|
553
+ next nil if part == refined
554
+ next part if base_disjoint?(base, part)
555
+
556
+ Type::Combinator.difference(part, refined)
557
+ end.compact
558
+
559
+ return current_type if survivors.empty?
560
+
561
+ Type::Combinator.union(*survivors)
562
+ end
563
+
529
564
  def falsey_value?(value)
530
565
  value.nil? || value == false
531
566
  end
@@ -1248,18 +1283,15 @@ module Rigor
1248
1283
  end
1249
1284
 
1250
1285
  def resolve_rbs_extended_method(node, scope)
1251
- loader = scope.environment.rbs_loader
1252
- return nil if loader.nil?
1253
-
1254
1286
  receiver_type = scope.type_of(node.receiver)
1255
1287
  class_name = rbs_extended_class_name(receiver_type)
1256
1288
  return nil if class_name.nil?
1257
- return nil unless loader.class_known?(class_name)
1289
+ return nil unless Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
1258
1290
 
1259
1291
  if receiver_type.is_a?(Type::Singleton)
1260
- loader.singleton_method(class_name: class_name, method_name: node.name)
1292
+ Rigor::Reflection.singleton_method_definition(class_name, node.name, scope: scope)
1261
1293
  else
1262
- loader.instance_method(class_name: class_name, method_name: node.name)
1294
+ Rigor::Reflection.instance_method_definition(class_name, node.name, scope: scope)
1263
1295
  end
1264
1296
  rescue StandardError
1265
1297
  nil