rigortype 0.2.1 → 0.2.3
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 +41 -14
- data/docs/handbook/01-getting-started.md +311 -0
- data/docs/handbook/02-everyday-types.md +337 -0
- data/docs/handbook/03-narrowing.md +359 -0
- data/docs/handbook/04-tuples-and-shapes.md +321 -0
- data/docs/handbook/05-methods-and-blocks.md +339 -0
- data/docs/handbook/06-classes.md +305 -0
- data/docs/handbook/07-rbs-and-extended.md +427 -0
- data/docs/handbook/08-understanding-errors.md +373 -0
- data/docs/handbook/09-plugins.md +241 -0
- data/docs/handbook/10-sorbet.md +347 -0
- data/docs/handbook/11-sig-gen.md +312 -0
- data/docs/handbook/12-lightweight-hkt.md +333 -0
- data/docs/handbook/README.md +275 -0
- data/docs/handbook/appendix-elixir.md +370 -0
- data/docs/handbook/appendix-go.md +399 -0
- data/docs/handbook/appendix-java-csharp.md +470 -0
- data/docs/handbook/appendix-liskov.md +580 -0
- data/docs/handbook/appendix-mypy.md +370 -0
- data/docs/handbook/appendix-phpstan.md +338 -0
- data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
- data/docs/handbook/appendix-rust.md +446 -0
- data/docs/handbook/appendix-steep.md +336 -0
- data/docs/handbook/appendix-type-theory.md +1662 -0
- data/docs/handbook/appendix-typeprof.md +416 -0
- data/docs/handbook/appendix-typescript.md +332 -0
- data/docs/install.md +189 -0
- data/docs/llms.txt +72 -0
- data/docs/manual/01-installation.md +342 -0
- data/docs/manual/02-cli-reference.md +569 -0
- data/docs/manual/03-configuration.md +152 -0
- data/docs/manual/04-diagnostics.md +206 -0
- data/docs/manual/05-inspecting-types.md +109 -0
- data/docs/manual/06-baseline.md +104 -0
- data/docs/manual/07-plugins.md +92 -0
- data/docs/manual/08-skills.md +143 -0
- data/docs/manual/09-editor-integration.md +245 -0
- data/docs/manual/10-mcp-server.md +539 -0
- data/docs/manual/11-ci.md +274 -0
- data/docs/manual/12-caching.md +116 -0
- data/docs/manual/13-troubleshooting.md +120 -0
- data/docs/manual/14-rails-quickstart.md +332 -0
- data/docs/manual/15-type-protection-coverage.md +204 -0
- data/docs/manual/16-rbs-extended-annotations.md +190 -0
- data/docs/manual/17-driving-improvement.md +160 -0
- data/docs/manual/README.md +87 -0
- data/docs/manual/ci-templates/README.md +58 -0
- data/docs/manual/plugins/README.md +86 -0
- data/docs/manual/plugins/rigor-actioncable.md +78 -0
- data/docs/manual/plugins/rigor-actionmailer.md +74 -0
- data/docs/manual/plugins/rigor-actionpack.md +80 -0
- data/docs/manual/plugins/rigor-activejob.md +58 -0
- data/docs/manual/plugins/rigor-activerecord.md +102 -0
- data/docs/manual/plugins/rigor-activestorage.md +74 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
- data/docs/manual/plugins/rigor-devise.md +70 -0
- data/docs/manual/plugins/rigor-dry-schema.md +56 -0
- data/docs/manual/plugins/rigor-dry-struct.md +60 -0
- data/docs/manual/plugins/rigor-dry-types.md +59 -0
- data/docs/manual/plugins/rigor-dry-validation.md +62 -0
- data/docs/manual/plugins/rigor-factorybot.md +76 -0
- data/docs/manual/plugins/rigor-graphql.md +89 -0
- data/docs/manual/plugins/rigor-hanami.md +83 -0
- data/docs/manual/plugins/rigor-mangrove.md +73 -0
- data/docs/manual/plugins/rigor-minitest.md +86 -0
- data/docs/manual/plugins/rigor-pundit.md +72 -0
- data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
- data/docs/manual/plugins/rigor-rails-routes.md +94 -0
- data/docs/manual/plugins/rigor-rails.md +44 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
- data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
- data/docs/manual/plugins/rigor-rspec.md +86 -0
- data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
- data/docs/manual/plugins/rigor-sidekiq.md +78 -0
- data/docs/manual/plugins/rigor-sinatra.md +61 -0
- data/docs/manual/plugins/rigor-sorbet.md +63 -0
- data/docs/manual/plugins/rigor-statesman.md +75 -0
- data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
- data/exe/rigor +1 -1
- data/lib/rigor/analysis/incremental_session.rb +4 -2
- data/lib/rigor/analysis/run_stats.rb +13 -1
- data/lib/rigor/analysis/runner.rb +54 -12
- data/lib/rigor/cli/check_command.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +248 -0
- data/lib/rigor/cli/skill_command.rb +103 -41
- data/lib/rigor/cli/skill_describe.rb +346 -0
- data/lib/rigor/cli/triage_command.rb +8 -2
- data/lib/rigor/cli/triage_renderer.rb +4 -0
- data/lib/rigor/cli.rb +25 -3
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +124 -32
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
- data/lib/rigor/inference/scope_indexer.rb +87 -89
- data/lib/rigor/plugin/isolation.rb +5 -5
- data/lib/rigor/plugin/loader.rb +4 -2
- data/lib/rigor/triage/catalogue.rb +16 -1
- data/lib/rigor/triage.rb +30 -7
- data/lib/rigor/version.rb +1 -1
- data/skills/rigor-ask/SKILL.md +172 -0
- data/skills/rigor-doctor/SKILL.md +87 -0
- data/skills/rigor-editor-setup/SKILL.md +114 -0
- data/skills/rigor-mcp-setup/SKILL.md +117 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
- data/skills/rigor-next-steps/SKILL.md +113 -0
- data/skills/rigor-plugin-tune/SKILL.md +79 -0
- data/skills/rigor-protection-uplift/SKILL.md +133 -0
- data/skills/rigor-rbs-setup/SKILL.md +128 -0
- data/skills/rigor-upgrade/SKILL.md +79 -0
- metadata +90 -1
|
@@ -51,7 +51,15 @@ module Rigor
|
|
|
51
51
|
NUMERIC_BINARY = Set[
|
|
52
52
|
:+, :-, :*, :/, :%, :**, :&, :|, :^, :<<, :>>,
|
|
53
53
|
:<, :<=, :>, :>=, :==, :!=, :<=>,
|
|
54
|
-
:gcd, :lcm, :fdiv, :quo, :ceildiv, :[]
|
|
54
|
+
:gcd, :lcm, :fdiv, :quo, :ceildiv, :[],
|
|
55
|
+
# Integer bit-test predicates (`(self & mask) <=> mask|0`). The
|
|
56
|
+
# catalog marks them `:dispatch` only because a non-Integer mask
|
|
57
|
+
# would route through `to_int`; a concrete Integer literal never
|
|
58
|
+
# does, so the fold is pure here — the sibling of the already-folded
|
|
59
|
+
# bit-reference `:[]`. Integer-only, but Float-safe to list: a Float
|
|
60
|
+
# receiver has no such method, so `invoke_binary` rescues the
|
|
61
|
+
# `NoMethodError` to nil and the RBS tier answers.
|
|
62
|
+
:allbits?, :anybits?, :nobits?
|
|
55
63
|
].freeze
|
|
56
64
|
STRING_BINARY = Set[
|
|
57
65
|
:+, :*, :==, :!=, :<, :<=, :>, :>=, :<=>,
|
|
@@ -86,6 +94,17 @@ module Rigor
|
|
|
86
94
|
# delegates to operand `==`); ordering is undefined for Complex.
|
|
87
95
|
COMPLEX_BINARY = Set[:+, :-, :*, :/, :**].freeze
|
|
88
96
|
|
|
97
|
+
# `Set#&` and its alias `Set#intersection` are leaf-pure for a
|
|
98
|
+
# concrete Set operand exactly like their siblings `|` / `-` / `^`
|
|
99
|
+
# (all `:leaf` in the catalog), but the catalog flags
|
|
100
|
+
# `set_i_intersection`'s C body `block_dependent` — it drives Set's
|
|
101
|
+
# own internal iterator — so the catalog tier declines and the
|
|
102
|
+
# intersection alone fails to fold. A concrete Set argument's `each`
|
|
103
|
+
# is the pure core method, so the fold is sound; the hand-rolled
|
|
104
|
+
# allow-list is the right tool, mirroring the bit-test predicates.
|
|
105
|
+
# (The other binary set ops keep folding through the catalog.)
|
|
106
|
+
SET_BINARY = Set[:&, :intersection].freeze
|
|
107
|
+
|
|
89
108
|
# v0.0.3 C — pure unary catalogue. Each method must:
|
|
90
109
|
# - take zero arguments,
|
|
91
110
|
# - have no side effects,
|
|
@@ -130,6 +149,19 @@ module Rigor
|
|
|
130
149
|
:abs, :magnitude, :floor, :ceil, :round, :truncate,
|
|
131
150
|
:next_float, :prev_float,
|
|
132
151
|
:to_s, :to_i, :to_int, :to_f, :to_r, :rationalize,
|
|
152
|
+
# `numerator` / `denominator` expose the rational
|
|
153
|
+
# decomposition of the float (`2.5.numerator → 5`,
|
|
154
|
+
# `.denominator → 2`) — pure arithmetic, the Float siblings
|
|
155
|
+
# of the already-folded Rational accessors. The non-finite
|
|
156
|
+
# edges stay sound: `Infinity.numerator → Infinity` /
|
|
157
|
+
# `.denominator → 1` fold to the same value Ruby returns, and
|
|
158
|
+
# `NaN.numerator → NaN` is declined by `foldable_constant_value?`.
|
|
159
|
+
:numerator, :denominator,
|
|
160
|
+
# `arg` / `angle` / `phase` (aliases) return the complex
|
|
161
|
+
# argument of the real number: `0` for `self >= 0`, `Math::PI`
|
|
162
|
+
# for `self < 0`. Pure sign test, deterministic; a NaN
|
|
163
|
+
# receiver yields NaN which `foldable_constant_value?` declines.
|
|
164
|
+
:arg, :angle, :phase,
|
|
133
165
|
:inspect, :-@, :+@
|
|
134
166
|
].freeze
|
|
135
167
|
STRING_UNARY = Set[
|
|
@@ -138,7 +170,12 @@ module Rigor
|
|
|
138
170
|
:empty?, :strip, :lstrip, :rstrip, :chomp, :chop, :squeeze,
|
|
139
171
|
:to_s, :to_str, :to_sym, :intern,
|
|
140
172
|
:to_i, :to_f, :ord, :chr, :hex, :oct, :succ, :next,
|
|
141
|
-
:sum, :inspect
|
|
173
|
+
:sum, :inspect,
|
|
174
|
+
# `shellescape` is the String-receiver twin of the already-folded
|
|
175
|
+
# `Shellwords.escape` — deterministic shell-quoting, no global
|
|
176
|
+
# state. The `shellwords` library is loaded process-wide via
|
|
177
|
+
# `shellwords_folding`, so the method is always defined here.
|
|
178
|
+
:shellescape
|
|
142
179
|
].freeze
|
|
143
180
|
SYMBOL_UNARY = Set[
|
|
144
181
|
:to_s, :to_sym, :to_proc, :length, :size,
|
|
@@ -386,26 +423,8 @@ module Rigor
|
|
|
386
423
|
end
|
|
387
424
|
|
|
388
425
|
def try_fold_unary_set(receiver_values, method_name)
|
|
389
|
-
|
|
390
|
-
return
|
|
391
|
-
|
|
392
|
-
string_lift = try_fold_string_array_unary(receiver_values, method_name)
|
|
393
|
-
return string_lift if string_lift
|
|
394
|
-
|
|
395
|
-
pathname_lift = try_fold_pathname_unary(receiver_values, method_name)
|
|
396
|
-
return pathname_lift if pathname_lift
|
|
397
|
-
|
|
398
|
-
regexp_lift = try_fold_regexp_array_unary(receiver_values, method_name)
|
|
399
|
-
return regexp_lift if regexp_lift
|
|
400
|
-
|
|
401
|
-
set_lift = try_fold_set_array_unary(receiver_values, method_name)
|
|
402
|
-
return set_lift if set_lift
|
|
403
|
-
|
|
404
|
-
integer_lift = try_fold_integer_array_unary(receiver_values, method_name)
|
|
405
|
-
return integer_lift if integer_lift
|
|
406
|
-
|
|
407
|
-
numeric_lift = try_fold_numeric_array_unary(receiver_values, method_name)
|
|
408
|
-
return numeric_lift if numeric_lift
|
|
426
|
+
special = try_fold_unary_special(receiver_values, method_name)
|
|
427
|
+
return special if special
|
|
409
428
|
|
|
410
429
|
# Type-level allow check on every receiver. If one member's
|
|
411
430
|
# type does not have the method in its allow list (e.g.
|
|
@@ -420,6 +439,23 @@ module Rigor
|
|
|
420
439
|
end
|
|
421
440
|
build_constant_type(results, source: receiver_values)
|
|
422
441
|
end
|
|
442
|
+
|
|
443
|
+
# The carrier-specific unary lifts — Range-to-Tuple, the
|
|
444
|
+
# Array-returning String / Pathname / Regexp / Set / Integer /
|
|
445
|
+
# Numeric folds — that produce a precise structural type before
|
|
446
|
+
# the generic scalar `invoke_unary` path. The first match wins;
|
|
447
|
+
# nil means none applied and the caller falls through to the
|
|
448
|
+
# scalar allow-list path.
|
|
449
|
+
def try_fold_unary_special(receiver_values, method_name)
|
|
450
|
+
try_fold_range_constant_unary(receiver_values, method_name) ||
|
|
451
|
+
try_fold_string_array_unary(receiver_values, method_name) ||
|
|
452
|
+
try_fold_pathname_unary(receiver_values, method_name) ||
|
|
453
|
+
try_fold_pathname_array_unary(receiver_values, method_name) ||
|
|
454
|
+
try_fold_regexp_array_unary(receiver_values, method_name) ||
|
|
455
|
+
try_fold_set_array_unary(receiver_values, method_name) ||
|
|
456
|
+
try_fold_integer_array_unary(receiver_values, method_name) ||
|
|
457
|
+
try_fold_numeric_array_unary(receiver_values, method_name)
|
|
458
|
+
end
|
|
423
459
|
# v0.0.7 — `Constant<Range>#to_a` and the no-arg
|
|
424
460
|
# `first` / `last` / `min` / `max` short-circuit through a
|
|
425
461
|
# Range-specific arm that catalog dispatch cannot reach:
|
|
@@ -436,10 +472,13 @@ module Rigor
|
|
|
436
472
|
RANGE_FOLD_METHODS = Set[:to_a, :first, :last, :min, :max, :count, :size, :length, :entries, :minmax,
|
|
437
473
|
:sum].freeze
|
|
438
474
|
# 1-arg head/tail projections on a `Constant<Range>`. `first(n)` /
|
|
439
|
-
# `take(n)` return the first `n` elements, `last(n)` the final `n
|
|
440
|
-
#
|
|
441
|
-
#
|
|
442
|
-
|
|
475
|
+
# `take(n)` return the first `n` elements, `last(n)` the final `n`,
|
|
476
|
+
# and `min(n)` / `max(n)` the n smallest / largest (for an ascending
|
|
477
|
+
# integer range `min(n) == first(n)` and `max(n) == last(n).reverse`)
|
|
478
|
+
# — each lifts to a per-position `Tuple[Constant[Integer]…]`. The
|
|
479
|
+
# no-arg `first` / `last` / `min` / `max` stay on the unary path
|
|
480
|
+
# (single Integer endpoint).
|
|
481
|
+
RANGE_FOLD_BINARY_METHODS = Set[:first, :last, :take, :min, :max].freeze
|
|
443
482
|
RANGE_TO_A_LIMIT = 16
|
|
444
483
|
private_constant :RANGE_FOLD_METHODS, :RANGE_FOLD_BINARY_METHODS, :RANGE_TO_A_LIMIT
|
|
445
484
|
|
|
@@ -518,17 +557,31 @@ module Rigor
|
|
|
518
557
|
|
|
519
558
|
def range_take_tuple(range, method_name, count)
|
|
520
559
|
return nil unless count.is_a?(Integer) && !count.negative?
|
|
521
|
-
# `first(n)`/`last(n)`/`take(n)` materialise at
|
|
522
|
-
# elements; cap that count so a huge `n` (or
|
|
523
|
-
# the Constant. `Range#size`
|
|
560
|
+
# `first(n)`/`last(n)`/`take(n)`/`min(n)`/`max(n)` materialise at
|
|
561
|
+
# most `min(n, size)` elements; cap that count so a huge `n` (or
|
|
562
|
+
# range) never blows up the Constant. `Range#size` and the head/
|
|
563
|
+
# tail projections are O(n) for integer endpoints (no full
|
|
564
|
+
# materialisation).
|
|
524
565
|
return nil if [count, range.size].min > RANGE_TO_A_LIMIT
|
|
525
566
|
|
|
526
|
-
values =
|
|
567
|
+
values = range_head_tail(range, method_name, count)
|
|
527
568
|
return Type::Combinator.tuple_of if values.empty?
|
|
528
569
|
|
|
529
570
|
Type::Combinator.tuple_of(*values.map { |v| Type::Combinator.constant_of(v) })
|
|
530
571
|
end
|
|
531
572
|
|
|
573
|
+
# The n elements a head/tail projection selects, in Ruby's order.
|
|
574
|
+
# For an ascending integer range `min(n)` is the leading `n`
|
|
575
|
+
# (`first(n)`) and `max(n)` the trailing `n` reversed (descending),
|
|
576
|
+
# so neither needs the full sort `Array#min`/`#max` would do.
|
|
577
|
+
def range_head_tail(range, method_name, count)
|
|
578
|
+
case method_name
|
|
579
|
+
when :last then range.last(count)
|
|
580
|
+
when :max then range.last(count).reverse
|
|
581
|
+
else range.first(count) # :first, :take, :min
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
532
585
|
def try_fold_binary_set(receiver_values, method_name, arg_values)
|
|
533
586
|
range_lift = try_fold_range_constant_binary(receiver_values, method_name, arg_values)
|
|
534
587
|
return range_lift if range_lift
|
|
@@ -559,7 +612,13 @@ module Rigor
|
|
|
559
612
|
# bounded for long strings. (`codepoints` yields per-character
|
|
560
613
|
# Integer codepoints, the sibling of the byte-valued `bytes`;
|
|
561
614
|
# `grapheme_clusters` is the extended-grapheme sibling of `chars`.)
|
|
562
|
-
|
|
615
|
+
# `shellsplit` is the String-receiver twin of the already-folded
|
|
616
|
+
# `Shellwords.split` — lifts the token Array to a Tuple. Raises
|
|
617
|
+
# `ArgumentError` on unmatched quotes, which `try_fold_string_array_unary`
|
|
618
|
+
# rescues to nil (RBS tier widens). `shellwords` is loaded process-wide
|
|
619
|
+
# via `shellwords_folding`.
|
|
620
|
+
STRING_ARRAY_UNARY_METHODS = Set[:chars, :bytes, :codepoints, :grapheme_clusters,
|
|
621
|
+
:lines, :split, :shellsplit].freeze
|
|
563
622
|
# `partition` / `rpartition` always return a fixed 3-element
|
|
564
623
|
# `[head, separator, tail]` Array whose members are substrings of
|
|
565
624
|
# the receiver (bounded by the input), so they lift to a precise
|
|
@@ -623,10 +682,26 @@ module Rigor
|
|
|
623
682
|
].freeze
|
|
624
683
|
PATHNAME_PURE_BINARY = Set[
|
|
625
684
|
:+, :join, :sub_ext, :<=>, :==, :eql?, :===,
|
|
626
|
-
:relative_path_from
|
|
685
|
+
:relative_path_from,
|
|
686
|
+
# `/` is the exact alias of `+` (`def /(other) = self + other`),
|
|
687
|
+
# the idiomatic path-join operator (`dir / "file"`). `basename`'s
|
|
688
|
+
# 1-arg suffix-stripping form (`path.basename(".rb")` → the stem)
|
|
689
|
+
# is the binary sibling of the already-folded no-arg `basename` —
|
|
690
|
+
# both are pure `@path` string manipulation, no filesystem read.
|
|
691
|
+
:/, :basename
|
|
627
692
|
].freeze
|
|
628
693
|
private_constant :PATHNAME_PURE_UNARY, :PATHNAME_PURE_BINARY
|
|
629
694
|
|
|
695
|
+
# `Constant<Pathname>#split` returns the fixed 2-element
|
|
696
|
+
# `[dirname, basename]` pair (both Pathname), the path-string
|
|
697
|
+
# split of `File.split`. Lifted to `Tuple[Constant[Pathname],
|
|
698
|
+
# Constant[Pathname]]`. Filesystem-independent — reads only
|
|
699
|
+
# `@path` — so it is deterministic at fold time, the
|
|
700
|
+
# Array-returning sibling of the scalar `basename` / `dirname`
|
|
701
|
+
# folds (which `try_fold_pathname_unary` already covers).
|
|
702
|
+
PATHNAME_ARRAY_UNARY_METHODS = Set[:split].freeze
|
|
703
|
+
private_constant :PATHNAME_ARRAY_UNARY_METHODS
|
|
704
|
+
|
|
630
705
|
def try_fold_pathname_unary(receiver_values, method_name)
|
|
631
706
|
return nil unless PATHNAME_PURE_UNARY.include?(method_name)
|
|
632
707
|
return nil unless receiver_values.size == 1
|
|
@@ -659,6 +734,22 @@ module Rigor
|
|
|
659
734
|
nil
|
|
660
735
|
end
|
|
661
736
|
|
|
737
|
+
# `Constant<Pathname>#split` — lift the `[dirname, basename]`
|
|
738
|
+
# Pathname pair to a Tuple[Constant[Pathname], Constant[Pathname]].
|
|
739
|
+
# Pure path-string manipulation (no filesystem read); both
|
|
740
|
+
# elements are Pathname, a foldable Constant class.
|
|
741
|
+
def try_fold_pathname_array_unary(receiver_values, method_name)
|
|
742
|
+
return nil unless PATHNAME_ARRAY_UNARY_METHODS.include?(method_name)
|
|
743
|
+
return nil unless receiver_values.size == 1
|
|
744
|
+
|
|
745
|
+
receiver = receiver_values.first
|
|
746
|
+
return nil unless receiver.is_a?(Pathname)
|
|
747
|
+
|
|
748
|
+
lift_array_result(receiver.split)
|
|
749
|
+
rescue StandardError
|
|
750
|
+
nil
|
|
751
|
+
end
|
|
752
|
+
|
|
662
753
|
def try_fold_string_array_unary(receiver_values, method_name)
|
|
663
754
|
return nil unless STRING_ARRAY_UNARY_METHODS.include?(method_name)
|
|
664
755
|
return nil unless receiver_values.size == 1
|
|
@@ -1500,6 +1591,7 @@ module Rigor
|
|
|
1500
1591
|
when nil then NIL_BINARY
|
|
1501
1592
|
when Rational then RATIONAL_BINARY
|
|
1502
1593
|
when Complex then COMPLEX_BINARY
|
|
1594
|
+
when ::Set then SET_BINARY
|
|
1503
1595
|
else Set.new
|
|
1504
1596
|
end
|
|
1505
1597
|
end
|
|
@@ -88,6 +88,7 @@ module Rigor
|
|
|
88
88
|
to_h: :tuple_to_h,
|
|
89
89
|
zip: :tuple_zip,
|
|
90
90
|
:[] => :tuple_index,
|
|
91
|
+
slice: :tuple_index,
|
|
91
92
|
fetch: :tuple_index,
|
|
92
93
|
dig: :tuple_dig,
|
|
93
94
|
values_at: :tuple_values_at,
|
|
@@ -842,7 +843,10 @@ module Rigor
|
|
|
842
843
|
|
|
843
844
|
# `tuple.min` / `tuple.max` — fold when every element is
|
|
844
845
|
# a `Constant` whose values share a Ruby-comparable
|
|
845
|
-
# domain. Empty tuples fold to `Constant[nil]`.
|
|
846
|
+
# domain. Empty tuples fold to `Constant[nil]`. The 1-arg
|
|
847
|
+
# `min(n)` / `max(n)` form folds to a `Tuple` of the n
|
|
848
|
+
# edge-most values in Ruby's order (`min(n)` ascending,
|
|
849
|
+
# `max(n)` descending) — the n-arg sibling of `first(n)`.
|
|
846
850
|
def tuple_min(tuple, _method_name, args)
|
|
847
851
|
tuple_minmax(tuple, args, :min)
|
|
848
852
|
end
|
|
@@ -852,7 +856,7 @@ module Rigor
|
|
|
852
856
|
end
|
|
853
857
|
|
|
854
858
|
def tuple_minmax(tuple, args, edge)
|
|
855
|
-
return
|
|
859
|
+
return tuple_minmax_n(tuple, args.first, edge) unless args.empty?
|
|
856
860
|
return Type::Combinator.constant_of(nil) if tuple.elements.empty?
|
|
857
861
|
|
|
858
862
|
values = constant_values(tuple.elements)
|
|
@@ -864,6 +868,25 @@ module Rigor
|
|
|
864
868
|
nil
|
|
865
869
|
end
|
|
866
870
|
|
|
871
|
+
# `tuple.min(n)` / `tuple.max(n)` — a `Tuple` of the n
|
|
872
|
+
# edge-most element values, delegating to Ruby's
|
|
873
|
+
# `Array#min` / `#max` for the ordering. Declines on a
|
|
874
|
+
# non-static / negative count or non-Constant elements.
|
|
875
|
+
# The result is bounded by the tuple's known arity, so no
|
|
876
|
+
# extra size cap is needed.
|
|
877
|
+
def tuple_minmax_n(tuple, arg, edge)
|
|
878
|
+
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer)
|
|
879
|
+
return nil if arg.value.negative?
|
|
880
|
+
|
|
881
|
+
values = constant_values(tuple.elements)
|
|
882
|
+
return nil if values.nil?
|
|
883
|
+
|
|
884
|
+
picked = values.public_send(edge, arg.value)
|
|
885
|
+
Type::Combinator.tuple_of(*picked.map { |v| Type::Combinator.constant_of(v) })
|
|
886
|
+
rescue StandardError
|
|
887
|
+
nil
|
|
888
|
+
end
|
|
889
|
+
|
|
867
890
|
# `tuple.minmax` — the `[min, max]` pair as a 2-slot
|
|
868
891
|
# `Tuple[Constant[min], Constant[max]]`, mirroring the
|
|
869
892
|
# `Range#minmax` fold. Every element must be a `Constant`
|
|
@@ -1201,6 +1224,13 @@ module Rigor
|
|
|
1201
1224
|
# indices still fall through because the same handler serves
|
|
1202
1225
|
# `fetch`, while statically nil slices can be represented
|
|
1203
1226
|
# precisely for `[]`.
|
|
1227
|
+
# `[]` and its exact alias `slice` share the index / Range /
|
|
1228
|
+
# start-length folding. `fetch` routes here too but stays
|
|
1229
|
+
# integer-index-only: the Range and start-length branches gate
|
|
1230
|
+
# on this selector set, which `fetch` is deliberately not in.
|
|
1231
|
+
SLICE_SELECTORS = Set[:[], :slice].freeze
|
|
1232
|
+
private_constant :SLICE_SELECTORS
|
|
1233
|
+
|
|
1204
1234
|
def tuple_index(tuple, method_name, args)
|
|
1205
1235
|
case args.size
|
|
1206
1236
|
when 1 then tuple_single_index(tuple, method_name, args.first)
|
|
@@ -1211,17 +1241,18 @@ module Rigor
|
|
|
1211
1241
|
def tuple_single_index(tuple, method_name, arg)
|
|
1212
1242
|
return nil unless arg.is_a?(Type::Constant)
|
|
1213
1243
|
|
|
1214
|
-
|
|
1215
|
-
return
|
|
1244
|
+
value = arg.value
|
|
1245
|
+
return tuple_range_slice(tuple, value) if SLICE_SELECTORS.include?(method_name) && value.is_a?(Range)
|
|
1246
|
+
return nil unless value.is_a?(Integer)
|
|
1216
1247
|
|
|
1217
|
-
idx = normalise_index(
|
|
1248
|
+
idx = normalise_index(value, tuple.elements.size)
|
|
1218
1249
|
return nil unless idx
|
|
1219
1250
|
|
|
1220
1251
|
tuple.elements[idx]
|
|
1221
1252
|
end
|
|
1222
1253
|
|
|
1223
1254
|
def tuple_start_length_slice(tuple, method_name, args)
|
|
1224
|
-
return nil unless method_name
|
|
1255
|
+
return nil unless SLICE_SELECTORS.include?(method_name)
|
|
1225
1256
|
|
|
1226
1257
|
start, length = args
|
|
1227
1258
|
return nil unless start.is_a?(Type::Constant) && length.is_a?(Type::Constant)
|
|
@@ -118,10 +118,12 @@ module Rigor
|
|
|
118
118
|
# table to suppress false positives for methods the
|
|
119
119
|
# user has defined but no RBS sig describes. Merged
|
|
120
120
|
# UNDER the cross-file pre-pass seed; details: merge_project_method_indexes.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
# One combined descent yields both the discovered-methods existence
|
|
122
|
+
# table and the instance def-node table — see
|
|
123
|
+
# {#build_methods_and_def_nodes}. `seed_discovered_methods` seeds the
|
|
124
|
+
# former onto the scope and returns the def-node table for
|
|
125
|
+
# `merge_project_method_indexes` below.
|
|
126
|
+
seeded_scope, file_def_nodes = seed_discovered_methods(seeded_scope, default_scope, root)
|
|
125
127
|
|
|
126
128
|
# v0.0.2 #5 + ADR-24 slice 2 — record per-instance-method
|
|
127
129
|
# def nodes, the class -> superclass map, and the
|
|
@@ -134,7 +136,7 @@ module Rigor
|
|
|
134
136
|
# table. Seeded inside `merge_project_method_indexes` so the
|
|
135
137
|
# per-file visibilities merge OVER the cross-file project seed
|
|
136
138
|
# rather than overwriting it.
|
|
137
|
-
seeded_scope = merge_project_method_indexes(seeded_scope, default_scope, root)
|
|
139
|
+
seeded_scope = merge_project_method_indexes(seeded_scope, default_scope, root, file_def_nodes)
|
|
138
140
|
|
|
139
141
|
table = {}.compare_by_identity
|
|
140
142
|
table.default = seeded_scope
|
|
@@ -160,6 +162,19 @@ module Rigor
|
|
|
160
162
|
table
|
|
161
163
|
end
|
|
162
164
|
|
|
165
|
+
# Runs the combined methods/def-nodes descent (one walk of the file),
|
|
166
|
+
# seeds the discovered-methods existence table onto `seeded_scope`
|
|
167
|
+
# (merged UNDER the cross-file pre-pass seed `default_scope` carries),
|
|
168
|
+
# and returns `[scope, file_def_nodes]` so the caller can thread the
|
|
169
|
+
# def-node table into {#merge_project_method_indexes} without walking
|
|
170
|
+
# the file a second time.
|
|
171
|
+
def seed_discovered_methods(seeded_scope, default_scope, root)
|
|
172
|
+
file_methods, file_def_nodes = build_methods_and_def_nodes(root)
|
|
173
|
+
discovered_methods = deep_merge_class_methods(default_scope.discovered_methods, file_methods)
|
|
174
|
+
scope = seeded_scope.with_discovery(seeded_scope.discovery.with(discovered_methods: discovered_methods))
|
|
175
|
+
[scope, file_def_nodes]
|
|
176
|
+
end
|
|
177
|
+
|
|
163
178
|
# ADR-48 Struct slice 3 — installs the top-level fold-safe-local set
|
|
164
179
|
# ({Inference::StructFoldSafety}). Struct member layouts of constant
|
|
165
180
|
# receivers are resolved through the side-table the seeded scope carries.
|
|
@@ -179,9 +194,9 @@ module Rigor
|
|
|
179
194
|
# `discovered_def_index_for_paths` seed carried on
|
|
180
195
|
# `default_scope` — same-file declarations win per entry,
|
|
181
196
|
# the cross-file seed supplies sibling-file ancestors.
|
|
182
|
-
def merge_project_method_indexes(seeded_scope, default_scope, root)
|
|
197
|
+
def merge_project_method_indexes(seeded_scope, default_scope, root, file_def_nodes)
|
|
183
198
|
def_nodes = default_scope.discovered_def_nodes.merge(
|
|
184
|
-
|
|
199
|
+
file_def_nodes
|
|
185
200
|
) { |_class, cross_file, per_file| cross_file.merge(per_file) }
|
|
186
201
|
singleton_def_nodes = default_scope.discovered_singleton_def_nodes.merge(
|
|
187
202
|
build_discovered_singleton_def_nodes(root)
|
|
@@ -1406,16 +1421,29 @@ module Rigor
|
|
|
1406
1421
|
Type::Combinator.singleton_of(full)
|
|
1407
1422
|
end
|
|
1408
1423
|
|
|
1409
|
-
# Slice 7 phase 12 — in-source method discovery pre-pass
|
|
1410
|
-
#
|
|
1411
|
-
#
|
|
1412
|
-
#
|
|
1413
|
-
#
|
|
1414
|
-
# `
|
|
1415
|
-
def
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1424
|
+
# Slice 7 phase 12 — in-source method discovery pre-pass, fused with
|
|
1425
|
+
# the instance-method def-node pre-pass (v0.0.2 #5). One descent
|
|
1426
|
+
# produces BOTH tables the per-file `index` and the cross-file
|
|
1427
|
+
# pre-pass each need together:
|
|
1428
|
+
#
|
|
1429
|
+
# - `methods` : `{class_name => {method => :instance | :singleton}}`
|
|
1430
|
+
# for every `def` / `define_method(:name)` / `attr_*` / `alias` /
|
|
1431
|
+
# Data/Struct-member reader (the undefined-method existence table).
|
|
1432
|
+
# - `def_nodes` : `{class_name => {method => Prism::DefNode}}` for
|
|
1433
|
+
# every instance-side `def` (the inter-procedural return-inference
|
|
1434
|
+
# table; singleton defs and `define_method` are intentionally
|
|
1435
|
+
# skipped — `record_def_node` filters them).
|
|
1436
|
+
#
|
|
1437
|
+
# `walk_methods` and `walk_def_nodes` had byte-identical class /
|
|
1438
|
+
# module / singleton / meta-block descents (both stop at `DefNode`),
|
|
1439
|
+
# so a single combined walk records both accumulators at once instead
|
|
1440
|
+
# of traversing every file twice.
|
|
1441
|
+
def build_methods_and_def_nodes(root)
|
|
1442
|
+
methods = {}
|
|
1443
|
+
def_nodes = {}
|
|
1444
|
+
walk_methods_and_def_nodes(root, [], false, methods, def_nodes)
|
|
1445
|
+
apply_alias_def_nodes(root, def_nodes)
|
|
1446
|
+
[methods.transform_values(&:freeze).freeze, def_nodes.transform_values(&:freeze).freeze]
|
|
1419
1447
|
end
|
|
1420
1448
|
|
|
1421
1449
|
# Merges two `class_name => { method => kind }` tables, unioning
|
|
@@ -1431,7 +1459,14 @@ module Rigor
|
|
|
1431
1459
|
end
|
|
1432
1460
|
|
|
1433
1461
|
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
1434
|
-
|
|
1462
|
+
# Combined `walk_methods` + `walk_def_nodes` descent. The two walks
|
|
1463
|
+
# had identical class / module / singleton-class / meta-block
|
|
1464
|
+
# traversals and both stopped at `DefNode`; the only divergences are
|
|
1465
|
+
# leaf actions (recorded into the right accumulator) and the original
|
|
1466
|
+
# `walk_methods` returning at `AliasMethodNode` (its symbol-only
|
|
1467
|
+
# children carry no def / class node, so not descending them is
|
|
1468
|
+
# byte-identical for `def_nodes` too). See {#build_methods_and_def_nodes}.
|
|
1469
|
+
def walk_methods_and_def_nodes(node, qualified_prefix, in_singleton_class, methods_acc, def_nodes_acc)
|
|
1435
1470
|
return unless node.is_a?(Prism::Node)
|
|
1436
1471
|
|
|
1437
1472
|
case node
|
|
@@ -1439,40 +1474,40 @@ module Rigor
|
|
|
1439
1474
|
name = Source::ConstantPath.qualified_name(node.constant_path)
|
|
1440
1475
|
if name
|
|
1441
1476
|
child_prefix = qualified_prefix + [name]
|
|
1442
|
-
record_meta_superclass_members(node, child_prefix,
|
|
1443
|
-
|
|
1477
|
+
record_meta_superclass_members(node, child_prefix, methods_acc) if node.is_a?(Prism::ClassNode)
|
|
1478
|
+
walk_methods_and_def_nodes(node.body, child_prefix, false, methods_acc, def_nodes_acc) if node.body
|
|
1444
1479
|
return
|
|
1445
1480
|
end
|
|
1446
1481
|
when Prism::SingletonClassNode
|
|
1447
1482
|
if node.body
|
|
1448
1483
|
singleton_prefix = singleton_class_prefix(node, qualified_prefix)
|
|
1449
1484
|
if singleton_prefix
|
|
1450
|
-
|
|
1485
|
+
walk_methods_and_def_nodes(node.body, singleton_prefix, true, methods_acc, def_nodes_acc)
|
|
1451
1486
|
return
|
|
1452
1487
|
end
|
|
1453
1488
|
end
|
|
1454
1489
|
when Prism::ConstantWriteNode
|
|
1455
1490
|
if meta_new_block_body(node)
|
|
1456
1491
|
child_prefix = qualified_prefix + [node.name.to_s]
|
|
1457
|
-
|
|
1492
|
+
walk_methods_and_def_nodes(meta_new_block_body(node), child_prefix, false, methods_acc, def_nodes_acc)
|
|
1458
1493
|
return
|
|
1459
1494
|
end
|
|
1460
1495
|
when Prism::DefNode
|
|
1461
|
-
record_def_method(node, qualified_prefix, in_singleton_class,
|
|
1496
|
+
record_def_method(node, qualified_prefix, in_singleton_class, methods_acc)
|
|
1497
|
+
record_def_node(node, qualified_prefix, in_singleton_class, def_nodes_acc)
|
|
1462
1498
|
return
|
|
1463
1499
|
when Prism::AliasMethodNode
|
|
1464
|
-
record_alias_method(node, qualified_prefix, in_singleton_class,
|
|
1500
|
+
record_alias_method(node, qualified_prefix, in_singleton_class, methods_acc)
|
|
1465
1501
|
return
|
|
1466
1502
|
when Prism::CallNode
|
|
1467
|
-
record_define_method(node, qualified_prefix, in_singleton_class,
|
|
1503
|
+
record_define_method(node, qualified_prefix, in_singleton_class, methods_acc) if node.name == :define_method
|
|
1468
1504
|
if ATTR_MACROS.include?(node.name)
|
|
1469
|
-
record_attr_methods(node, qualified_prefix, in_singleton_class,
|
|
1470
|
-
accumulator)
|
|
1505
|
+
record_attr_methods(node, qualified_prefix, in_singleton_class, methods_acc)
|
|
1471
1506
|
end
|
|
1472
1507
|
end
|
|
1473
1508
|
|
|
1474
1509
|
node.compact_child_nodes.each do |child|
|
|
1475
|
-
|
|
1510
|
+
walk_methods_and_def_nodes(child, qualified_prefix, in_singleton_class, methods_acc, def_nodes_acc)
|
|
1476
1511
|
end
|
|
1477
1512
|
end
|
|
1478
1513
|
|
|
@@ -1606,57 +1641,6 @@ module Rigor
|
|
|
1606
1641
|
end
|
|
1607
1642
|
end
|
|
1608
1643
|
|
|
1609
|
-
# v0.0.2 #5 — instance-side def-node recording. Walks
|
|
1610
|
-
# class bodies the same way as `build_discovered_methods`
|
|
1611
|
-
# but records the actual `Prism::DefNode` for each
|
|
1612
|
-
# **instance** method so `ExpressionTyper` can re-type
|
|
1613
|
-
# the body at the call site for inter-procedural return
|
|
1614
|
-
# inference. Singleton methods and `define_method` calls
|
|
1615
|
-
# are intentionally skipped: the inference path needs a
|
|
1616
|
-
# statically introspectable body, and singleton dispatch
|
|
1617
|
-
# has its own complications (Class / Module ancestry)
|
|
1618
|
-
# the first-iteration rule does not yet model.
|
|
1619
|
-
def build_discovered_def_nodes(root)
|
|
1620
|
-
accumulator = {}
|
|
1621
|
-
walk_def_nodes(root, [], false, accumulator)
|
|
1622
|
-
apply_alias_def_nodes(root, accumulator)
|
|
1623
|
-
accumulator.transform_values(&:freeze).freeze
|
|
1624
|
-
end
|
|
1625
|
-
|
|
1626
|
-
def walk_def_nodes(node, qualified_prefix, in_singleton_class, accumulator)
|
|
1627
|
-
return unless node.is_a?(Prism::Node)
|
|
1628
|
-
|
|
1629
|
-
case node
|
|
1630
|
-
when Prism::ClassNode, Prism::ModuleNode
|
|
1631
|
-
name = Source::ConstantPath.qualified_name(node.constant_path)
|
|
1632
|
-
if name
|
|
1633
|
-
child_prefix = qualified_prefix + [name]
|
|
1634
|
-
walk_def_nodes(node.body, child_prefix, false, accumulator) if node.body
|
|
1635
|
-
return
|
|
1636
|
-
end
|
|
1637
|
-
when Prism::SingletonClassNode
|
|
1638
|
-
if node.body
|
|
1639
|
-
singleton_prefix = singleton_class_prefix(node, qualified_prefix)
|
|
1640
|
-
if singleton_prefix
|
|
1641
|
-
walk_def_nodes(node.body, singleton_prefix, true, accumulator)
|
|
1642
|
-
return
|
|
1643
|
-
end
|
|
1644
|
-
end
|
|
1645
|
-
when Prism::ConstantWriteNode
|
|
1646
|
-
if meta_new_block_body(node)
|
|
1647
|
-
child_prefix = qualified_prefix + [node.name.to_s]
|
|
1648
|
-
walk_def_nodes(meta_new_block_body(node), child_prefix, false, accumulator)
|
|
1649
|
-
return
|
|
1650
|
-
end
|
|
1651
|
-
when Prism::DefNode
|
|
1652
|
-
record_def_node(node, qualified_prefix, in_singleton_class, accumulator)
|
|
1653
|
-
return
|
|
1654
|
-
end
|
|
1655
|
-
|
|
1656
|
-
node.compact_child_nodes.each do |child|
|
|
1657
|
-
walk_def_nodes(child, qualified_prefix, in_singleton_class, accumulator)
|
|
1658
|
-
end
|
|
1659
|
-
end
|
|
1660
1644
|
# v0.0.3 A — sentinel key under which `record_def_node`
|
|
1661
1645
|
# files DefNodes that live outside any class / module
|
|
1662
1646
|
# body (top-level helpers, `def`s nested inside DSL
|
|
@@ -2385,7 +2369,13 @@ module Rigor
|
|
|
2385
2369
|
# the override-visibility-reduced rule can read an ancestor's
|
|
2386
2370
|
# visibility declared in a sibling file.
|
|
2387
2371
|
def accumulate_project_index(acc, path, root)
|
|
2388
|
-
|
|
2372
|
+
# One combined descent yields both the methods existence table and
|
|
2373
|
+
# the def-node table; the latter is also consumed by
|
|
2374
|
+
# `record_class_sources`, so a def-dense file is walked once here
|
|
2375
|
+
# instead of three times (methods + def-nodes ×2). See
|
|
2376
|
+
# {#build_methods_and_def_nodes}.
|
|
2377
|
+
file_methods, file_def_nodes = build_methods_and_def_nodes(root)
|
|
2378
|
+
merge_discovered_defs(acc[:def_nodes], acc[:def_sources], path, file_def_nodes)
|
|
2389
2379
|
build_discovered_singleton_def_nodes(root).each do |class_name, methods|
|
|
2390
2380
|
(acc[:singleton_def_nodes][class_name] ||= {}).merge!(methods)
|
|
2391
2381
|
end
|
|
@@ -2395,20 +2385,28 @@ module Rigor
|
|
|
2395
2385
|
includes.each do |class_name, mods|
|
|
2396
2386
|
acc[:includes][class_name] = ((acc[:includes][class_name] || []) + mods).uniq
|
|
2397
2387
|
end
|
|
2398
|
-
record_class_sources(acc[:class_sources], path, root, superclasses, includes)
|
|
2399
|
-
merge_class_keyed_index_tables(acc, root)
|
|
2388
|
+
record_class_sources(acc[:class_sources], path, root, superclasses, includes, file_def_nodes)
|
|
2389
|
+
merge_class_keyed_index_tables(acc, root, file_methods)
|
|
2390
|
+
merge_member_layout_tables(acc, root)
|
|
2391
|
+
end
|
|
2392
|
+
|
|
2393
|
+
# Folds one file's Data + Struct member-layout tables into the
|
|
2394
|
+
# cross-file accumulator (kept out of {#accumulate_project_index} to
|
|
2395
|
+
# hold its ABC budget).
|
|
2396
|
+
def merge_member_layout_tables(acc, root)
|
|
2400
2397
|
acc[:data_member_layouts].merge!(build_data_member_layouts(root))
|
|
2401
2398
|
acc[:struct_member_layouts].merge!(build_struct_member_layouts(root))
|
|
2402
2399
|
end
|
|
2403
2400
|
|
|
2404
2401
|
# Folds the per-class method-visibility and method-existence tables of
|
|
2405
2402
|
# one file into the cross-file accumulator (kept out of
|
|
2406
|
-
# {#accumulate_project_index} to hold its ABC budget).
|
|
2407
|
-
def
|
|
2403
|
+
# {#accumulate_project_index} to hold its ABC budget). `file_methods`
|
|
2404
|
+
# is the existence table from the combined methods/def-nodes descent.
|
|
2405
|
+
def merge_class_keyed_index_tables(acc, root, file_methods)
|
|
2408
2406
|
build_discovered_method_visibilities(root).each do |class_name, table|
|
|
2409
2407
|
(acc[:method_visibilities][class_name] ||= {}).merge!(table)
|
|
2410
2408
|
end
|
|
2411
|
-
|
|
2409
|
+
file_methods.each do |class_name, table|
|
|
2412
2410
|
(acc[:methods][class_name] ||= {}).merge!(table)
|
|
2413
2411
|
end
|
|
2414
2412
|
end
|
|
@@ -2423,13 +2421,13 @@ module Rigor
|
|
|
2423
2421
|
# dependency recording (ADR-46). The class-declaration walk
|
|
2424
2422
|
# (`collect_class_decls`) catches bodyless / def-less reopenings the
|
|
2425
2423
|
# other three builders miss.
|
|
2426
|
-
def record_class_sources(class_sources, path, root, superclasses, includes)
|
|
2424
|
+
def record_class_sources(class_sources, path, root, superclasses, includes, file_def_nodes)
|
|
2427
2425
|
names = Set.new
|
|
2428
2426
|
collect_class_decls(root, [], decls = {})
|
|
2429
2427
|
names.merge(decls.keys)
|
|
2430
2428
|
names.merge(superclasses.keys)
|
|
2431
2429
|
names.merge(includes.keys)
|
|
2432
|
-
names.merge(
|
|
2430
|
+
names.merge(file_def_nodes.keys)
|
|
2433
2431
|
names.each { |name| (class_sources[name] ||= Set.new) << path }
|
|
2434
2432
|
end
|
|
2435
2433
|
|
|
@@ -2438,8 +2436,8 @@ module Rigor
|
|
|
2438
2436
|
# seen `"path:line"` definition site in `def_sources` (ADR-17 —
|
|
2439
2437
|
# the un-registered-project-patch signal `call.undefined-method`
|
|
2440
2438
|
# and `rigor triage` key on).
|
|
2441
|
-
def merge_discovered_defs(def_nodes, def_sources, path,
|
|
2442
|
-
|
|
2439
|
+
def merge_discovered_defs(def_nodes, def_sources, path, file_def_nodes)
|
|
2440
|
+
file_def_nodes.each do |class_name, methods|
|
|
2443
2441
|
(def_nodes[class_name] ||= {}).merge!(methods)
|
|
2444
2442
|
sources = (def_sources[class_name] ||= {})
|
|
2445
2443
|
methods.each do |method_name, def_node|
|
|
@@ -12,13 +12,13 @@ module Rigor
|
|
|
12
12
|
# `exe/rigor` launcher maps `.rigor.yml`'s `plugins_isolation:` onto it
|
|
13
13
|
# before re-exec). Three backends behind one interface:
|
|
14
14
|
#
|
|
15
|
-
# - `none`
|
|
16
|
-
# Lowest cost; no isolation.
|
|
17
|
-
# invoked library is trusted + pure.
|
|
15
|
+
# - `none` — load into the main space and call directly.
|
|
16
|
+
# Lowest cost; no isolation. Used as the fallback where fork is
|
|
17
|
+
# unavailable; fine because the invoked library is trusted + pure.
|
|
18
18
|
# - `ruby_box` — call inside a {Box} (`Ruby::Box`, `RUBY_BOX=1`). Isolates
|
|
19
19
|
# core-class monkey-patches + lets gem versions coexist, but a native
|
|
20
20
|
# crash in the boxed work still takes the process down (in-process).
|
|
21
|
-
# - `process` — call in a forked worker ({Process}); returns data over a
|
|
21
|
+
# - `process` (**default**) — call in a forked worker ({Process}); returns data over a
|
|
22
22
|
# pipe. The strongest: a child crash (even `SIGSEGV`) is contained —
|
|
23
23
|
# the parent survives and declines. Higher cost (fork + IPC).
|
|
24
24
|
#
|
|
@@ -73,7 +73,7 @@ module Rigor
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
# `none` — load the trusted library into the main space and call it
|
|
76
|
-
# directly. No isolation; lowest cost; the
|
|
76
|
+
# directly. No isolation; lowest cost; the fork-unavailable fallback.
|
|
77
77
|
module Direct
|
|
78
78
|
module_function
|
|
79
79
|
|