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.
- checksums.yaml +4 -4
- data/README.md +159 -224
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +29 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +169 -23
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules.rb +266 -63
- data/lib/rigor/analysis/diagnostic.rb +8 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +2 -1
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -1
- data/lib/rigor/analysis/runner.rb +58 -21
- data/lib/rigor/analysis/worker_session.rb +21 -11
- data/lib/rigor/bleeding_edge.rb +123 -0
- data/lib/rigor/cache/descriptor.rb +86 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cli/annotate_command.rb +100 -15
- data/lib/rigor/cli/check_command.rb +3 -0
- data/lib/rigor/cli/plugins_command.rb +2 -4
- data/lib/rigor/cli/plugins_renderer.rb +0 -2
- data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
- data/lib/rigor/cli/triage_command.rb +6 -3
- data/lib/rigor/cli/triage_renderer.rb +15 -1
- data/lib/rigor/cli.rb +9 -1
- data/lib/rigor/configuration/severity_profile.rb +13 -1
- data/lib/rigor/configuration.rb +57 -1
- data/lib/rigor/environment/rbs_loader.rb +25 -0
- data/lib/rigor/inference/body_fixpoint.rb +89 -0
- data/lib/rigor/inference/budget_trace.rb +29 -2
- data/lib/rigor/inference/expression_typer.rb +1052 -43
- data/lib/rigor/inference/macro_block_self_type.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +54 -14
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +148 -10
- data/lib/rigor/inference/method_dispatcher.rb +72 -1
- data/lib/rigor/inference/method_parameter_binder.rb +56 -2
- data/lib/rigor/inference/multi_target_binder.rb +46 -3
- data/lib/rigor/inference/mutation_widening.rb +142 -0
- data/lib/rigor/inference/narrowing.rb +270 -37
- data/lib/rigor/inference/scope_indexer.rb +696 -25
- data/lib/rigor/inference/statement_evaluator.rb +963 -16
- data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
- data/lib/rigor/plugin/base.rb +235 -79
- data/lib/rigor/plugin/macro/block_as_method.rb +22 -21
- data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
- data/lib/rigor/plugin/macro.rb +2 -3
- data/lib/rigor/plugin/manifest.rb +4 -24
- data/lib/rigor/plugin/node_rule_walk.rb +59 -14
- data/lib/rigor/plugin/registry.rb +12 -11
- data/lib/rigor/scope/discovery_index.rb +2 -0
- data/lib/rigor/scope.rb +132 -6
- data/lib/rigor/sig_gen/generator.rb +8 -0
- data/lib/rigor/triage/catalogue.rb +4 -19
- data/lib/rigor/triage.rb +69 -1
- data/lib/rigor/type/combinator.rb +29 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +13 -29
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +27 -90
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +20 -19
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +10 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +11 -40
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +21 -34
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +11 -18
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -13
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +25 -0
- data/sig/rigor/analysis/fact_store.rbs +3 -0
- data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
- data/sig/rigor/plugin/base.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +1 -2
- data/sig/rigor/scope.rbs +10 -1
- data/sig/rigor/type.rbs +1 -0
- data/sig/rigor.rbs +1 -1
- data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
- data/skills/rigor-plugin-author/SKILL.md +6 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
- metadata +7 -2
- 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
|
-
|
|
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
|