rigortype 0.2.5 → 0.2.6
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/docs/handbook/09-plugins.md +5 -2
- data/docs/handbook/appendix-liskov.md +5 -3
- data/docs/handbook/appendix-phpstan.md +2 -2
- data/docs/install.md +1 -1
- data/docs/manual/02-cli-reference.md +58 -1
- data/docs/manual/06-baseline.md +12 -0
- data/docs/manual/11-ci.md +6 -6
- data/docs/manual/15-type-protection-coverage.md +29 -0
- data/docs/manual/plugins/rigor-minitest.md +1 -1
- data/lib/rigor/cli/check_command.rb +4 -33
- data/lib/rigor/cli/check_runner_factory.rb +63 -0
- data/lib/rigor/cli/doctor_command.rb +295 -0
- data/lib/rigor/cli/plugins_command.rb +2 -2
- data/lib/rigor/cli/plugins_renderer.rb +1 -1
- data/lib/rigor/cli/protection_renderer.rb +32 -2
- data/lib/rigor/cli/protection_report.rb +32 -6
- data/lib/rigor/cli/upgrade_command.rb +25 -0
- data/lib/rigor/cli.rb +17 -1
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/inference/dynamic_origin.rb +67 -0
- data/lib/rigor/inference/expression_typer.rb +22 -10
- data/lib/rigor/inference/fallback.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +16 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +41 -2
- data/lib/rigor/inference/method_dispatcher.rb +19 -4
- data/lib/rigor/inference/mutation_widening.rb +18 -0
- data/lib/rigor/inference/protection_scanner.rb +6 -3
- data/lib/rigor/inference/statement_evaluator.rb +5 -4
- data/lib/rigor/plugin/base.rb +34 -7
- data/lib/rigor/plugin/registry.rb +1 -1
- data/lib/rigor/scope.rb +16 -5
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +1 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +3 -3
- data/sig/rigor/plugin/base.rbs +2 -0
- data/sig/rigor/scope.rbs +3 -1
- data/skills/rigor-plugin-author/SKILL.md +8 -5
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +8 -4
- metadata +5 -1
|
@@ -1861,13 +1861,13 @@ module Rigor
|
|
|
1861
1861
|
end
|
|
1862
1862
|
|
|
1863
1863
|
# ADR-37 slice 2 / ADR-52 WD3 — gathers each plugin's post-return
|
|
1864
|
-
# narrowing from the method-gated `
|
|
1864
|
+
# narrowing from the method-gated `narrowing_facts` DSL, wrapped as
|
|
1865
1865
|
# a facts-only `FlowContribution`, swallowing per-plugin
|
|
1866
1866
|
# exceptions so a buggy plugin can't abort the assertion path.
|
|
1867
1867
|
EMPTY_CONTRIBUTIONS = [].freeze
|
|
1868
1868
|
private_constant :EMPTY_CONTRIBUTIONS
|
|
1869
1869
|
|
|
1870
|
-
# Fast-exit guard: skip if no plugin declares a `
|
|
1870
|
+
# Fast-exit guard: skip if no plugin declares a `narrowing_facts` rule, or if
|
|
1871
1871
|
# no registered method-name gate matches the call. See
|
|
1872
1872
|
# `collect_gated_statement_contributions` for the full consultation.
|
|
1873
1873
|
def collect_plugin_contributions(registry, call_node, current_scope)
|
|
@@ -1882,7 +1882,7 @@ module Rigor
|
|
|
1882
1882
|
end
|
|
1883
1883
|
|
|
1884
1884
|
# ADR-37 slice 2 / ADR-52 WD1 — post-gate walk in registry order.
|
|
1885
|
-
# Visits only plugins in `for_statement` (declare a `
|
|
1885
|
+
# Visits only plugins in `for_statement` (declare a `narrowing_facts` rule),
|
|
1886
1886
|
# further gated by the method-name Set probe so the common no-candidate
|
|
1887
1887
|
# case is a single lookup. Accumulates lazily; caller is read-only.
|
|
1888
1888
|
def collect_gated_statement_contributions(index, relevant, name, call_node, current_scope)
|
|
@@ -2918,7 +2918,8 @@ module Rigor
|
|
|
2918
2918
|
environment: scope.environment,
|
|
2919
2919
|
locals: {}.freeze,
|
|
2920
2920
|
source_path: scope.source_path,
|
|
2921
|
-
discovery: scope.discovery
|
|
2921
|
+
discovery: scope.discovery,
|
|
2922
|
+
dynamic_origins: scope.dynamic_origins
|
|
2922
2923
|
)
|
|
2923
2924
|
end
|
|
2924
2925
|
|
data/lib/rigor/plugin/base.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Rigor
|
|
|
20
20
|
#
|
|
21
21
|
# This class implements all plugin protocol hooks: per-call
|
|
22
22
|
# return-type contributions (`dynamic_return`), narrowing-fact
|
|
23
|
-
# contributions (`
|
|
23
|
+
# contributions (`narrowing_facts`), AST node rules (`node_rule`),
|
|
24
24
|
# and producer/cache hooks. Cumulative implementation per the
|
|
25
25
|
# ADR-37 / ADR-52 slice chain.
|
|
26
26
|
#
|
|
@@ -440,19 +440,32 @@ module Rigor
|
|
|
440
440
|
# `post_return_facts` slot of the deleted `flow_contribution_for`
|
|
441
441
|
# hook (ADR-52 WD3):
|
|
442
442
|
#
|
|
443
|
-
#
|
|
443
|
+
# narrowing_facts methods: [:assert_kind_of] do |call_node, scope|
|
|
444
444
|
# # return an Array of post-return facts, or nil
|
|
445
445
|
# end
|
|
446
446
|
#
|
|
447
447
|
# `methods:` is a non-empty Array of method names; the engine
|
|
448
448
|
# calls the block only when `call_node.name` is one of them. The
|
|
449
449
|
# block returns the same `post_return_facts` the merger applies.
|
|
450
|
-
|
|
451
|
-
|
|
450
|
+
#
|
|
451
|
+
# This hook supplies post-return *narrowing facts* — the
|
|
452
|
+
# predicate/assertion edges a call establishes about its
|
|
453
|
+
# arguments or receiver, e.g. `assert_kind_of(String, x)` ⇒ `x`
|
|
454
|
+
# is narrowed to `String` on the continuation. It does NOT give a
|
|
455
|
+
# call a *type*; for that use {dynamic_return} (per-call-site) or
|
|
456
|
+
# contribute RBS via the manifest `signature_paths:`.
|
|
457
|
+
# `dynamic_return` is the type slot, this is the fact slot.
|
|
458
|
+
#
|
|
459
|
+
# Renamed from `type_specifier` (ADR-80): the old name read as a
|
|
460
|
+
# parallel to `dynamic_return` (a type) when it actually returns
|
|
461
|
+
# facts. {.type_specifier} survives as a deprecating alias through
|
|
462
|
+
# 0.2.x and is removed in 0.3.0.
|
|
463
|
+
def narrowing_facts(methods:, &block)
|
|
464
|
+
raise ArgumentError, "Plugin::Base.narrowing_facts requires a block body" if block.nil?
|
|
452
465
|
unless methods.is_a?(Array) && !methods.empty? &&
|
|
453
466
|
methods.all? { |m| m.is_a?(Symbol) || (m.is_a?(String) && !m.empty?) }
|
|
454
467
|
raise ArgumentError,
|
|
455
|
-
"Plugin::Base.
|
|
468
|
+
"Plugin::Base.narrowing_facts methods: must be a non-empty Array of Symbol/String, " \
|
|
456
469
|
"got #{methods.inspect}"
|
|
457
470
|
end
|
|
458
471
|
|
|
@@ -461,6 +474,20 @@ module Rigor
|
|
|
461
474
|
nil
|
|
462
475
|
end
|
|
463
476
|
|
|
477
|
+
# DEPRECATED (ADR-80) — renamed to {.narrowing_facts}. This hook
|
|
478
|
+
# supplies post-return narrowing *facts*, not a type; the old
|
|
479
|
+
# name misleads by parallel with {.dynamic_return}. Retained as a
|
|
480
|
+
# warning-emitting alias through 0.2.x; REMOVED in 0.3.0. Migrate
|
|
481
|
+
# `type_specifier methods: …` → `narrowing_facts methods: …`.
|
|
482
|
+
def type_specifier(methods:, &)
|
|
483
|
+
unless @type_specifier_deprecation_warned
|
|
484
|
+
@type_specifier_deprecation_warned = true
|
|
485
|
+
warn("[rigor] Plugin::Base.type_specifier is deprecated (ADR-80) and will be " \
|
|
486
|
+
"removed in Rigor 0.3.0; rename it to `narrowing_facts`. (#{name})")
|
|
487
|
+
end
|
|
488
|
+
narrowing_facts(methods:, &)
|
|
489
|
+
end
|
|
490
|
+
|
|
464
491
|
# Frozen snapshot of the declared type-specifier rules. Memoised
|
|
465
492
|
# for the same reason as {dynamic_returns} — consulted per plugin
|
|
466
493
|
# per dispatch, over an array fixed at class-definition time.
|
|
@@ -504,7 +531,7 @@ module Rigor
|
|
|
504
531
|
# production users migrated. Per-call return types are declared via
|
|
505
532
|
# the gated {.dynamic_return} DSL (static / run-time / per-file
|
|
506
533
|
# name sets, static / run-time receiver sets); post-return
|
|
507
|
-
# narrowing facts via {.
|
|
534
|
+
# narrowing facts via {.narrowing_facts}. See the CHANGELOG
|
|
508
535
|
# migration note for the idiom-by-idiom mapping.
|
|
509
536
|
|
|
510
537
|
# ADR-9 slice 3 — per-run preparation hook. The runner
|
|
@@ -625,7 +652,7 @@ module Rigor
|
|
|
625
652
|
end
|
|
626
653
|
|
|
627
654
|
# ADR-37 slice 2 — the post-return narrowing facts contributed by
|
|
628
|
-
# this plugin's {.
|
|
655
|
+
# this plugin's {.narrowing_facts} rules for a call. The engine
|
|
629
656
|
# calls this from `StatementEvaluator`; a rule fires only when
|
|
630
657
|
# `call_node.name`
|
|
631
658
|
# is one of its declared `methods:`. Failures isolate to [].
|
|
@@ -148,7 +148,7 @@ module Rigor
|
|
|
148
148
|
"plugin #{(plugin.class.name || plugin.class).inspect} defines `flow_contribution_for`, " \
|
|
149
149
|
"which was removed (ADR-52). Declare the per-call return type via `dynamic_return` " \
|
|
150
150
|
"(receivers:/methods:/file_methods: gates, static or callable) and post-return narrowing " \
|
|
151
|
-
"facts via `
|
|
151
|
+
"facts via `narrowing_facts` — see the CHANGELOG migration note."
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
# Same `Method#owner` trick for the per-file diagnostics hook —
|
data/lib/rigor/scope.rb
CHANGED
|
@@ -23,7 +23,8 @@ module Rigor
|
|
|
23
23
|
:ivars, :cvars, :globals,
|
|
24
24
|
:indexed_narrowings, :method_chain_narrowings,
|
|
25
25
|
:declaration_sourced,
|
|
26
|
-
:source_path, :discovery, :struct_fold_safe_locals
|
|
26
|
+
:source_path, :discovery, :struct_fold_safe_locals,
|
|
27
|
+
:dynamic_origins
|
|
27
28
|
|
|
28
29
|
# ADR-53 Track A — the seed-time discovery tables live on the
|
|
29
30
|
# {DiscoveryIndex} the scope carries by a single reference; the
|
|
@@ -122,6 +123,11 @@ module Rigor
|
|
|
122
123
|
end
|
|
123
124
|
end
|
|
124
125
|
|
|
126
|
+
def record_dynamic_origin(node, cause)
|
|
127
|
+
@dynamic_origins[node] = cause
|
|
128
|
+
self
|
|
129
|
+
end
|
|
130
|
+
|
|
125
131
|
def initialize(
|
|
126
132
|
environment:, locals:,
|
|
127
133
|
fact_store: Analysis::FactStore.empty,
|
|
@@ -134,7 +140,8 @@ module Rigor
|
|
|
134
140
|
method_chain_narrowings: EMPTY_CHAIN_NARROWINGS,
|
|
135
141
|
declaration_sourced: EMPTY_DECLARATION_SOURCED,
|
|
136
142
|
source_path: nil,
|
|
137
|
-
struct_fold_safe_locals: EMPTY_FOLD_SAFE
|
|
143
|
+
struct_fold_safe_locals: EMPTY_FOLD_SAFE,
|
|
144
|
+
dynamic_origins: {}.compare_by_identity
|
|
138
145
|
)
|
|
139
146
|
@environment = environment
|
|
140
147
|
@locals = locals
|
|
@@ -149,6 +156,7 @@ module Rigor
|
|
|
149
156
|
@declaration_sourced = declaration_sourced
|
|
150
157
|
@source_path = source_path
|
|
151
158
|
@struct_fold_safe_locals = struct_fold_safe_locals
|
|
159
|
+
@dynamic_origins = dynamic_origins
|
|
152
160
|
freeze
|
|
153
161
|
end
|
|
154
162
|
|
|
@@ -716,7 +724,8 @@ module Rigor
|
|
|
716
724
|
method_chain_narrowings: @method_chain_narrowings,
|
|
717
725
|
declaration_sourced: @declaration_sourced,
|
|
718
726
|
source_path: @source_path,
|
|
719
|
-
struct_fold_safe_locals: @struct_fold_safe_locals
|
|
727
|
+
struct_fold_safe_locals: @struct_fold_safe_locals,
|
|
728
|
+
dynamic_origins: @dynamic_origins
|
|
720
729
|
)
|
|
721
730
|
self.class.new(
|
|
722
731
|
environment: environment, locals: locals,
|
|
@@ -727,7 +736,8 @@ module Rigor
|
|
|
727
736
|
method_chain_narrowings: method_chain_narrowings,
|
|
728
737
|
declaration_sourced: declaration_sourced,
|
|
729
738
|
source_path: source_path,
|
|
730
|
-
struct_fold_safe_locals: struct_fold_safe_locals
|
|
739
|
+
struct_fold_safe_locals: struct_fold_safe_locals,
|
|
740
|
+
dynamic_origins: dynamic_origins
|
|
731
741
|
)
|
|
732
742
|
end
|
|
733
743
|
|
|
@@ -764,7 +774,8 @@ module Rigor
|
|
|
764
774
|
# flow-live (a method-local nil write / failed-guard narrowing), the
|
|
765
775
|
# merge is flow-live and `possible-nil-receiver` fires as before.
|
|
766
776
|
declaration_sourced: join_declaration_sourced(other),
|
|
767
|
-
source_path: source_path
|
|
777
|
+
source_path: source_path,
|
|
778
|
+
dynamic_origins: @dynamic_origins
|
|
768
779
|
)
|
|
769
780
|
end
|
|
770
781
|
|
data/lib/rigor/version.rb
CHANGED
data/lib/rigor.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "rigor/environment"
|
|
|
10
10
|
require_relative "rigor/rbs_extended"
|
|
11
11
|
require_relative "rigor/testing"
|
|
12
12
|
require_relative "rigor/inference/budget_trace"
|
|
13
|
+
require_relative "rigor/inference/dynamic_origin"
|
|
13
14
|
require_relative "rigor/inference/fallback"
|
|
14
15
|
require_relative "rigor/inference/fallback_tracer"
|
|
15
16
|
require_relative "rigor/inference/acceptance"
|
|
@@ -145,7 +145,7 @@ module Rigor
|
|
|
145
145
|
private_constant :SPEC_MATCHER_FORM
|
|
146
146
|
|
|
147
147
|
# ADR-37 slice 2 — the method names this analyzer narrows on,
|
|
148
|
-
# for the plugin's `
|
|
148
|
+
# for the plugin's `narrowing_facts methods:` gate.
|
|
149
149
|
SUPPORTED_METHODS = (ASSERT_FORM.keys + SPEC_MATCHER_FORM.keys).freeze
|
|
150
150
|
|
|
151
151
|
def spec_form_fact(call_node, environment:)
|
|
@@ -78,7 +78,7 @@ module Rigor
|
|
|
78
78
|
# assertion, method-gated by the engine. The engine routes
|
|
79
79
|
# `:local`-kind facts through
|
|
80
80
|
# `StatementEvaluator#apply_local_post_return_fact`.
|
|
81
|
-
|
|
81
|
+
narrowing_facts methods: AssertionAnalyzer::SUPPORTED_METHODS do |call_node, scope|
|
|
82
82
|
AssertionAnalyzer.contribution_for(call_node, environment: scope&.environment)&.post_return_facts
|
|
83
83
|
end
|
|
84
84
|
end
|
|
@@ -110,7 +110,7 @@ module Rigor
|
|
|
110
110
|
# ADR-37 slice 2 — matcher narrowing
|
|
111
111
|
# (`expect(x).to be_a(T)` → `post_return_facts` on `x`),
|
|
112
112
|
# method-gated by the engine on the expectation verbs.
|
|
113
|
-
|
|
113
|
+
narrowing_facts methods: %i[to not_to to_not] do |call_node, scope|
|
|
114
114
|
MatcherAnalyzer.contribution_for(call_node, environment: scope&.environment)&.post_return_facts
|
|
115
115
|
end
|
|
116
116
|
|
|
@@ -169,13 +169,13 @@ module Rigor
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
# ADR-52 slice 4 — `T.bind(self, T)`'s self-narrowing fact,
|
|
172
|
-
# contributed via the method-gated `
|
|
172
|
+
# contributed via the method-gated `narrowing_facts` DSL. The
|
|
173
173
|
# statement evaluator consults this path for narrowing facts.
|
|
174
174
|
# The return-type half (`Constant[nil]`) flows through the
|
|
175
175
|
# `dynamic_return` rule above; the block re-checks the `T.`
|
|
176
176
|
# receiver via the recogniser, so an unrelated `bind` call
|
|
177
177
|
# contributes nothing.
|
|
178
|
-
|
|
178
|
+
narrowing_facts methods: [:bind] do |call_node, scope|
|
|
179
179
|
bind_post_return_facts(call_node, scope)
|
|
180
180
|
end
|
|
181
181
|
|
|
@@ -258,7 +258,7 @@ module Rigor
|
|
|
258
258
|
lookup_signature(call_node, scope)&.return_type
|
|
259
259
|
end
|
|
260
260
|
|
|
261
|
-
# The `
|
|
261
|
+
# The `narrowing_facts` body for `T.bind` — same sigil gate as
|
|
262
262
|
# the return-type path, then the recogniser's
|
|
263
263
|
# `post_return_facts` (the `Fact(target_kind: :self)` that
|
|
264
264
|
# narrows `scope.self_type` for the rest of the block).
|
data/sig/rigor/plugin/base.rbs
CHANGED
|
@@ -26,6 +26,8 @@ class Rigor::Plugin::Base
|
|
|
26
26
|
def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
27
27
|
def self.dynamic_returns: () -> Array[untyped]
|
|
28
28
|
|
|
29
|
+
def self.narrowing_facts: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
30
|
+
# Deprecating alias for `narrowing_facts` (ADR-80); removed in 0.3.0.
|
|
29
31
|
def self.type_specifier: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
30
32
|
def self.type_specifiers: () -> Array[untyped]
|
|
31
33
|
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -8,6 +8,7 @@ module Rigor
|
|
|
8
8
|
attr_reader cvars: Hash[Symbol, Type::t]
|
|
9
9
|
attr_reader globals: Hash[Symbol, Type::t]
|
|
10
10
|
attr_reader discovery: DiscoveryIndex
|
|
11
|
+
attr_reader dynamic_origins: Hash[untyped, Symbol]
|
|
11
12
|
attr_reader indexed_narrowings: Hash[IndexedKey, Type::t]
|
|
12
13
|
attr_reader method_chain_narrowings: Hash[ChainKey, Type::t]
|
|
13
14
|
attr_reader source_path: String?
|
|
@@ -32,6 +33,7 @@ module Rigor
|
|
|
32
33
|
def data_member_layouts: () -> Hash[String, Array[Symbol]]
|
|
33
34
|
def struct_member_layouts: () -> Hash[String, { members: Array[Symbol], keyword_init: bool }]
|
|
34
35
|
def param_inferred_types: () -> Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
36
|
+
def record_dynamic_origin: (untyped node, Symbol cause) -> Scope
|
|
35
37
|
|
|
36
38
|
class DiscoveryIndex
|
|
37
39
|
attr_reader declared_types: Hash[untyped, Type::t]
|
|
@@ -71,7 +73,7 @@ module Rigor
|
|
|
71
73
|
|
|
72
74
|
def self.empty: (?environment: Environment, ?source_path: String?) -> Scope
|
|
73
75
|
|
|
74
|
-
def initialize: (environment: Environment, locals: Hash[Symbol, Type::t], ?fact_store: Analysis::FactStore, ?self_type: Type::t?, ?ivars: Hash[Symbol, Type::t], ?cvars: Hash[Symbol, Type::t], ?globals: Hash[Symbol, Type::t], ?discovery: DiscoveryIndex, ?source_path: String?) -> void
|
|
76
|
+
def initialize: (environment: Environment, locals: Hash[Symbol, Type::t], ?fact_store: Analysis::FactStore, ?self_type: Type::t?, ?ivars: Hash[Symbol, Type::t], ?cvars: Hash[Symbol, Type::t], ?globals: Hash[Symbol, Type::t], ?discovery: DiscoveryIndex, ?source_path: String?, ?dynamic_origins: Hash[untyped, Symbol]) -> void
|
|
75
77
|
def with_source_path: (String? path) -> Scope
|
|
76
78
|
def with_struct_fold_safe: (Set[Symbol] locals) -> Scope
|
|
77
79
|
def struct_fold_safe?: (String | Symbol name) -> bool
|
|
@@ -97,19 +97,22 @@ AST walk per file — hands every matching node to the block along with a
|
|
|
97
97
|
`scope` it can query for inferred types. The block returns an array of
|
|
98
98
|
`Rigor::Analysis::Diagnostic` (built via the `diagnostic` helper).
|
|
99
99
|
Optionally the plugin also declares `dynamic_return(receivers:)` /
|
|
100
|
-
`
|
|
101
|
-
for call sites the core analyzer types as `Dynamic`.
|
|
100
|
+
`narrowing_facts(methods:)` to *supply* a return type or narrowing facts
|
|
101
|
+
for call sites the core analyzer types as `Dynamic`. (`narrowing_facts`
|
|
102
|
+
was renamed from `type_specifier` in ADR-80; `type_specifier` remains as a
|
|
103
|
+
deprecating alias removed in 0.3.0 — use `narrowing_facts` in new plugins.)
|
|
104
|
+
`#diagnostics_for_file`
|
|
102
105
|
is the file-rule surface for whole-file diagnostics a per-node walk can't
|
|
103
106
|
express. (`flow_contribution_for` was removed pre-1.0 in ADR-52 WD3 —
|
|
104
107
|
defining it now raises `ArgumentError`; use `dynamic_return` /
|
|
105
|
-
`
|
|
108
|
+
`narrowing_facts`. See Phase 2.)
|
|
106
109
|
|
|
107
110
|
## Phase outline
|
|
108
111
|
|
|
109
112
|
| Phase | What | Reference |
|
|
110
113
|
| --- | --- | --- |
|
|
111
114
|
| 1 | Package and scaffold — gem vs project-private layout, gemspec / Gemfile, the plugin class skeleton, `.rigor.yml` activation. | [`references/01-plan-and-scaffold.md`](references/01-plan-and-scaffold.md) |
|
|
112
|
-
| 2 | Node rules — `node_rule` (engine-owned walk), building `Diagnostic`s via `Base#diagnostic`, querying `scope.type_of`, calling the target library directly instead of reimplementing it (ADR-39: `Plugin::Inflector`, `Base.suggest`), optional `dynamic_return` / `
|
|
115
|
+
| 2 | Node rules — `node_rule` (engine-owned walk), building `Diagnostic`s via `Base#diagnostic`, querying `scope.type_of`, calling the target library directly instead of reimplementing it (ADR-39: `Plugin::Inflector`, `Base.suggest`), optional `dynamic_return` / `narrowing_facts`, RBS for the DSL. | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) |
|
|
113
116
|
| 3 | Test and ship — fixture-based tests (RSpec / Minitest, no rigor internals), version pinning, README, publish or keep private. | [`references/03-test-and-ship.md`](references/03-test-and-ship.md) |
|
|
114
117
|
|
|
115
118
|
## Reading order — modules
|
|
@@ -117,5 +120,5 @@ defining it now raises `ArgumentError`; use `dynamic_return` /
|
|
|
117
120
|
| Module | Read | Covers |
|
|
118
121
|
| --- | --- | --- |
|
|
119
122
|
| 1 | [`references/01-plan-and-scaffold.md`](references/01-plan-and-scaffold.md) | **Phase 1.** The gem vs project-private packaging split, directory trees for both, gemspec template, project-private path-gem / `RUBYLIB` activation, the `Rigor::Plugin::Base` skeleton, `.rigor.yml` `plugins:` wiring. |
|
|
120
|
-
| 2 | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) | **Phase 2.** The `node_rule` engine-owned AST walk over Prism nodes, the `Base#diagnostic` helper, asking the analyzer for inferred types via `scope.type_of`, two-pass / lexical context (`node_file_context` / `NodeContext`), the optional `dynamic_return` / `
|
|
123
|
+
| 2 | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) | **Phase 2.** The `node_rule` engine-owned AST walk over Prism nodes, the `Base#diagnostic` helper, asking the analyzer for inferred types via `scope.type_of`, two-pass / lexical context (`node_file_context` / `NodeContext`), the optional `dynamic_return` / `narrowing_facts` return-type hooks (`narrowing_facts` was renamed from `type_specifier` in ADR-80, alias removed in 0.3.0; `flow_contribution_for` was removed pre-1.0 in ADR-52 WD3), calling the target library's pure methods directly rather than reimplementing them (ADR-39: `Plugin::Inflector` over the real `ActiveSupport::Inflector`; `Base.suggest` for did-you-mean), and shipping `sig/*.rbs` so the DSL's types are visible. |
|
|
121
124
|
| 3 | [`references/03-test-and-ship.md`](references/03-test-and-ship.md) | **Phase 3.** Testing a plugin from outside the monorepo — fixture projects driven through `rigor check --format json`, plus pure unit tests of dispatch tables — with RSpec or Minitest. Version pinning against the pre-1.0 contract. README. Publishing to RubyGems or keeping the plugin private. |
|
|
@@ -162,7 +162,7 @@ rather than hand-rolling Levenshtein:
|
|
|
162
162
|
Rigor::Plugin::Base.suggest(typo, known_names) # nearest match, or nil
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
## Optional — contribute a return type with `dynamic_return` / `
|
|
165
|
+
## Optional — contribute a return type with `dynamic_return` / `narrowing_facts`
|
|
166
166
|
|
|
167
167
|
> **Critical — these hooks do NOT make a method "defined", so they do
|
|
168
168
|
> NOT suppress `call.undefined-method`.** Method *existence* and call
|
|
@@ -197,11 +197,15 @@ end
|
|
|
197
197
|
# Post-return NARROWING FACTS, gated on the call's method name.
|
|
198
198
|
# Return an Array of facts (or nil). Used for assertion / predicate
|
|
199
199
|
# narrowing (`assert_kind_of(Foo, x)` ⇒ x is Foo afterwards).
|
|
200
|
-
|
|
200
|
+
narrowing_facts methods: [:assert_kind_of] do |call_node, scope|
|
|
201
201
|
# ... build and return the post-return facts ...
|
|
202
202
|
end
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
+
> **`narrowing_facts` was renamed from `type_specifier` in ADR-80.**
|
|
206
|
+
> `type_specifier` remains as a deprecating alias, removed in 0.3.0 — use
|
|
207
|
+
> `narrowing_facts` in new plugins.
|
|
208
|
+
|
|
205
209
|
Build return types with `Rigor::Type::Combinator`:
|
|
206
210
|
|
|
207
211
|
```ruby
|
|
@@ -240,7 +244,7 @@ If the DSL introduces methods or classes that Rigor cannot see (a
|
|
|
240
244
|
Rigor RBS declaring them so *core* inference — not just your plugin —
|
|
241
245
|
treats them as **defined**. This is what removes the
|
|
242
246
|
`call.undefined-method` diagnostics on those methods; nothing else
|
|
243
|
-
(not a `node_rule`, not `dynamic_return` / `
|
|
247
|
+
(not a `node_rule`, not `dynamic_return` / `narrowing_facts`) makes a
|
|
244
248
|
method exist in Rigor's view.
|
|
245
249
|
|
|
246
250
|
Two ways to wire the RBS, depending on how the plugin is packaged:
|
|
@@ -288,6 +292,6 @@ They compose — many plugins ship both.
|
|
|
288
292
|
|
|
289
293
|
A plugin whose `node_rule`(s) recognise the DSL and emit diagnostics
|
|
290
294
|
with correct severities and rule ids — optionally a `dynamic_return` /
|
|
291
|
-
`
|
|
295
|
+
`narrowing_facts` and a `sig/` bundle. Verify by eye with `rigor check`;
|
|
292
296
|
lock it down with tests in Phase 3
|
|
293
297
|
([`03-test-and-ship.md`](03-test-and-ship.md)).
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rigortype
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rigor contributors
|
|
@@ -460,6 +460,7 @@ files:
|
|
|
460
460
|
- lib/rigor/cli/annotate_command.rb
|
|
461
461
|
- lib/rigor/cli/baseline_command.rb
|
|
462
462
|
- lib/rigor/cli/check_command.rb
|
|
463
|
+
- lib/rigor/cli/check_runner_factory.rb
|
|
463
464
|
- lib/rigor/cli/ci_detector.rb
|
|
464
465
|
- lib/rigor/cli/command.rb
|
|
465
466
|
- lib/rigor/cli/coverage_command.rb
|
|
@@ -470,6 +471,7 @@ files:
|
|
|
470
471
|
- lib/rigor/cli/diagnostic_formats.rb
|
|
471
472
|
- lib/rigor/cli/diff_command.rb
|
|
472
473
|
- lib/rigor/cli/docs_command.rb
|
|
474
|
+
- lib/rigor/cli/doctor_command.rb
|
|
473
475
|
- lib/rigor/cli/explain_command.rb
|
|
474
476
|
- lib/rigor/cli/fused_protection_renderer.rb
|
|
475
477
|
- lib/rigor/cli/fused_protection_report.rb
|
|
@@ -498,6 +500,7 @@ files:
|
|
|
498
500
|
- lib/rigor/cli/type_scan_command.rb
|
|
499
501
|
- lib/rigor/cli/type_scan_renderer.rb
|
|
500
502
|
- lib/rigor/cli/type_scan_report.rb
|
|
503
|
+
- lib/rigor/cli/upgrade_command.rb
|
|
501
504
|
- lib/rigor/config_audit.rb
|
|
502
505
|
- lib/rigor/configuration.rb
|
|
503
506
|
- lib/rigor/configuration/dependencies.rb
|
|
@@ -547,6 +550,7 @@ files:
|
|
|
547
550
|
- lib/rigor/inference/closure_escape_analyzer.rb
|
|
548
551
|
- lib/rigor/inference/coverage_scanner.rb
|
|
549
552
|
- lib/rigor/inference/def_return_typer.rb
|
|
553
|
+
- lib/rigor/inference/dynamic_origin.rb
|
|
550
554
|
- lib/rigor/inference/expression_typer.rb
|
|
551
555
|
- lib/rigor/inference/fallback.rb
|
|
552
556
|
- lib/rigor/inference/fallback_tracer.rb
|