rigortype 0.1.18 → 0.1.19

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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +159 -224
  3. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
  4. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
  5. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +29 -0
  6. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
  7. data/lib/rigor/analysis/check_rules/rule_walk.rb +169 -23
  8. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
  9. data/lib/rigor/analysis/check_rules.rb +266 -63
  10. data/lib/rigor/analysis/diagnostic.rb +8 -0
  11. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +2 -1
  12. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -1
  13. data/lib/rigor/analysis/runner.rb +58 -21
  14. data/lib/rigor/analysis/worker_session.rb +21 -11
  15. data/lib/rigor/bleeding_edge.rb +123 -0
  16. data/lib/rigor/cache/descriptor.rb +86 -8
  17. data/lib/rigor/cache/rbs_descriptor.rb +2 -1
  18. data/lib/rigor/cli/annotate_command.rb +100 -15
  19. data/lib/rigor/cli/check_command.rb +3 -0
  20. data/lib/rigor/cli/plugins_command.rb +2 -4
  21. data/lib/rigor/cli/plugins_renderer.rb +0 -2
  22. data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
  23. data/lib/rigor/cli/triage_command.rb +6 -3
  24. data/lib/rigor/cli/triage_renderer.rb +15 -1
  25. data/lib/rigor/cli.rb +9 -1
  26. data/lib/rigor/configuration/severity_profile.rb +13 -1
  27. data/lib/rigor/configuration.rb +57 -1
  28. data/lib/rigor/environment/rbs_loader.rb +25 -0
  29. data/lib/rigor/inference/body_fixpoint.rb +89 -0
  30. data/lib/rigor/inference/budget_trace.rb +29 -2
  31. data/lib/rigor/inference/expression_typer.rb +1052 -43
  32. data/lib/rigor/inference/macro_block_self_type.rb +2 -2
  33. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
  34. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +54 -14
  35. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
  36. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
  37. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +148 -10
  38. data/lib/rigor/inference/method_dispatcher.rb +72 -1
  39. data/lib/rigor/inference/method_parameter_binder.rb +56 -2
  40. data/lib/rigor/inference/multi_target_binder.rb +46 -3
  41. data/lib/rigor/inference/mutation_widening.rb +142 -0
  42. data/lib/rigor/inference/narrowing.rb +270 -37
  43. data/lib/rigor/inference/scope_indexer.rb +696 -25
  44. data/lib/rigor/inference/statement_evaluator.rb +963 -16
  45. data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
  46. data/lib/rigor/plugin/base.rb +235 -79
  47. data/lib/rigor/plugin/macro/block_as_method.rb +22 -21
  48. data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
  49. data/lib/rigor/plugin/macro.rb +2 -3
  50. data/lib/rigor/plugin/manifest.rb +4 -24
  51. data/lib/rigor/plugin/node_rule_walk.rb +59 -14
  52. data/lib/rigor/plugin/registry.rb +12 -11
  53. data/lib/rigor/scope/discovery_index.rb +2 -0
  54. data/lib/rigor/scope.rb +132 -6
  55. data/lib/rigor/sig_gen/generator.rb +8 -0
  56. data/lib/rigor/triage/catalogue.rb +4 -19
  57. data/lib/rigor/triage.rb +69 -1
  58. data/lib/rigor/type/combinator.rb +29 -0
  59. data/lib/rigor/version.rb +1 -1
  60. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +13 -29
  61. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
  62. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +27 -90
  63. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
  64. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +20 -19
  65. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +10 -8
  66. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +11 -40
  67. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +1 -1
  68. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
  69. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +21 -34
  70. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +11 -18
  71. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
  72. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -13
  73. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
  74. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
  75. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
  76. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +25 -0
  77. data/sig/rigor/analysis/fact_store.rbs +3 -0
  78. data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
  79. data/sig/rigor/plugin/base.rbs +5 -2
  80. data/sig/rigor/plugin/manifest.rbs +1 -2
  81. data/sig/rigor/scope.rbs +10 -1
  82. data/sig/rigor/type.rbs +1 -0
  83. data/sig/rigor.rbs +1 -1
  84. data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
  85. data/skills/rigor-plugin-author/SKILL.md +6 -4
  86. data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
  87. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
  88. metadata +7 -2
  89. data/lib/rigor/plugin/macro/external_file.rb +0 -143
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../type"
4
+ require_relative "budget_trace"
5
+
6
+ module Rigor
7
+ module Inference
8
+ # ADR-56 WD3 — the single capped-fixpoint mechanism shared by the
9
+ # non-escaping block captured-local write-back (slice A) and the
10
+ # loop-body fixpoint (slice B). Computes the continuation binding of a
11
+ # set of locals that a body (a block body or a loop body) may rebind
12
+ # across an unknown number (0..N) of iterations.
13
+ #
14
+ # The body may run zero times, so the seed (pre-state) binding is kept
15
+ # as a join constituent throughout; it may compound (`a = [a]`), so the
16
+ # join is iterated to a fixed point under a hard cap (ADR-41 WD4). On
17
+ # the final permitted iteration value-pinned constituents are widened to
18
+ # their nominal base to force convergence; a local that still moves is
19
+ # collapsed to `Dynamic[top]` — the established escaping-block floor —
20
+ # and a {BudgetTrace::BLOCK_WRITEBACK_CAP} hit is recorded.
21
+ #
22
+ # The mechanism is parameterized over an `evaluate_body` callable so
23
+ # slice B can reuse it: given the current per-name bindings it returns
24
+ # the per-name exit bindings produced by one body evaluation from those
25
+ # bindings (names the body leaves unwritten in a given pass simply do
26
+ # not appear in the returned hash).
27
+ module BodyFixpoint
28
+ # One body evaluation per iteration; ADR-55's shape (cap 3).
29
+ CAP = 3
30
+
31
+ module_function
32
+
33
+ # @param names [Array<Symbol>] the outer locals the body can rebind.
34
+ # @param seed_bindings [Hash{Symbol=>Type}] pre-state binding per name.
35
+ # @param widen [#call] value-pinned widener (Constant -> Nominal).
36
+ # @param evaluate_body [#call] `bindings -> exit_bindings` — evaluates
37
+ # the body once from `bindings` (the per-name current assumption) and
38
+ # returns the per-name exit binding it produced.
39
+ # @return [Hash{Symbol=>Type}] the continuation binding per name.
40
+ def converge(names:, seed_bindings:, widen:, evaluate_body:)
41
+ return {} if names.empty?
42
+
43
+ # Running assumption per name; seeded with the pre-state binding,
44
+ # which stays a join constituent throughout (0-iteration soundness).
45
+ assumption = seed_bindings.dup
46
+
47
+ (0...CAP).each do |iteration|
48
+ last_iteration = iteration == CAP - 1
49
+ exit_bindings = evaluate_body.call(assumption)
50
+
51
+ stable = true
52
+ names.each do |name|
53
+ exit_type = exit_bindings[name]
54
+ next if exit_type.nil? # body did not write it this pass
55
+
56
+ if last_iteration
57
+ # On the final permitted iteration widen BOTH the running
58
+ # assumption and the fresh exit type to their nominal bases
59
+ # before joining: Rigor's `union` keeps `Constant[1]` and
60
+ # `Nominal[Integer]` as distinct members, so an accumulator
61
+ # (`+=`/`*=`) producing a fresh constant per pass would never
62
+ # converge without collapsing both sides first. If the join
63
+ # is still wider than the widened assumption (structural
64
+ # compounding, `a = [a]`), the local floors to `Dynamic[top]`.
65
+ base = widen.call(assumption[name])
66
+ joined = Type::Combinator.union(base, widen.call(exit_type))
67
+ if joined == base
68
+ assumption[name] = joined
69
+ else
70
+ BudgetTrace.hit(BudgetTrace::BLOCK_WRITEBACK_CAP)
71
+ assumption[name] = Type::Combinator.untyped
72
+ end
73
+ else
74
+ joined = Type::Combinator.union(assumption[name], exit_type)
75
+ next if joined == assumption[name]
76
+
77
+ stable = false
78
+ assumption[name] = joined
79
+ end
80
+ end
81
+
82
+ break if stable
83
+ end
84
+
85
+ assumption
86
+ end
87
+ end
88
+ end
89
+ end
@@ -20,6 +20,12 @@ module Rigor
20
20
  # hit the 100-node BFS cap and gave up resolving the self-call.
21
21
  # - {HKT_FUEL_EXHAUSTED} — `HktReducer` ran out of its reduction
22
22
  # fuel budget and unwound to `app.bound`.
23
+ # - {RECURSION_UNROLL_FUEL} — the constant-arg recursion unroll
24
+ # (ADR-55 slice 1) exhausted its per-entry fuel and fell back to
25
+ # the plain `(receiver, method)` guard (in-cycle call → `Dynamic[top]`).
26
+ # - {RECURSION_FIXPOINT_CAP} — the fixpoint return-summary iteration
27
+ # (ADR-55 slice 2) hit its 3-evaluation cap without converging and
28
+ # collapsed the summary to `untyped` (today's behaviour).
23
29
  #
24
30
  # Enabled only when `RIGOR_BUDGET_TRACE` is set (to any non-empty
25
31
  # value) in the environment, or via {enable!} in tests. When
@@ -34,8 +40,29 @@ module Rigor
34
40
  RECURSION_GUARD = :recursion_guard
35
41
  ANCESTOR_WALK_LIMIT = :ancestor_walk_limit
36
42
  HKT_FUEL_EXHAUSTED = :hkt_fuel_exhausted
37
-
38
- CATEGORIES = [RECURSION_GUARD, ANCESTOR_WALK_LIMIT, HKT_FUEL_EXHAUSTED].freeze
43
+ # `ExpressionTyper#infer_user_method_return` exhausted its
44
+ # constant-arg unroll fuel (ADR-55 slice 1) and fell back to the
45
+ # plain `(receiver, method)` recursion guard — i.e. the in-cycle
46
+ # call widened to `Dynamic[top]` exactly as it does without the
47
+ # unroll.
48
+ RECURSION_UNROLL_FUEL = :recursion_unroll_fuel
49
+ # `ExpressionTyper#infer_user_method_return` ran the fixpoint
50
+ # return-summary iteration (ADR-55 slice 2) to its 3-evaluation cap
51
+ # without reaching convergence and collapsed the summary to
52
+ # `untyped` — the in-cycle result widens to `Dynamic[top]` exactly
53
+ # as it does without the fixpoint.
54
+ RECURSION_FIXPOINT_CAP = :recursion_fixpoint_cap
55
+ # `BodyFixpoint#converge` (ADR-56 slice A — non-escaping block
56
+ # captured-local write-back) ran its 3-evaluation cap without the
57
+ # written local's join converging and collapsed that local to
58
+ # `Dynamic[top]` (the escaping-block floor). Shared by slice B's
59
+ # loop-body fixpoint.
60
+ BLOCK_WRITEBACK_CAP = :block_writeback_cap
61
+
62
+ CATEGORIES = [
63
+ RECURSION_GUARD, ANCESTOR_WALK_LIMIT, HKT_FUEL_EXHAUSTED, RECURSION_UNROLL_FUEL,
64
+ RECURSION_FIXPOINT_CAP, BLOCK_WRITEBACK_CAP
65
+ ].freeze
39
66
 
40
67
  # Distribution (histogram) categories — read-only observations of
41
68
  # a value's size at a site, used to choose budget defaults from an