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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/docs/handbook/09-plugins.md +5 -2
  3. data/docs/handbook/appendix-liskov.md +5 -3
  4. data/docs/handbook/appendix-phpstan.md +2 -2
  5. data/docs/install.md +1 -1
  6. data/docs/manual/02-cli-reference.md +58 -1
  7. data/docs/manual/06-baseline.md +12 -0
  8. data/docs/manual/11-ci.md +6 -6
  9. data/docs/manual/15-type-protection-coverage.md +29 -0
  10. data/docs/manual/plugins/rigor-minitest.md +1 -1
  11. data/lib/rigor/cli/check_command.rb +4 -33
  12. data/lib/rigor/cli/check_runner_factory.rb +63 -0
  13. data/lib/rigor/cli/doctor_command.rb +295 -0
  14. data/lib/rigor/cli/plugins_command.rb +2 -2
  15. data/lib/rigor/cli/plugins_renderer.rb +1 -1
  16. data/lib/rigor/cli/protection_renderer.rb +32 -2
  17. data/lib/rigor/cli/protection_report.rb +32 -6
  18. data/lib/rigor/cli/upgrade_command.rb +25 -0
  19. data/lib/rigor/cli.rb +17 -1
  20. data/lib/rigor/flow_contribution/fact.rb +1 -1
  21. data/lib/rigor/inference/dynamic_origin.rb +67 -0
  22. data/lib/rigor/inference/expression_typer.rb +22 -10
  23. data/lib/rigor/inference/fallback.rb +2 -2
  24. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +16 -0
  25. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +41 -2
  26. data/lib/rigor/inference/method_dispatcher.rb +19 -4
  27. data/lib/rigor/inference/mutation_widening.rb +18 -0
  28. data/lib/rigor/inference/protection_scanner.rb +6 -3
  29. data/lib/rigor/inference/statement_evaluator.rb +5 -4
  30. data/lib/rigor/plugin/base.rb +34 -7
  31. data/lib/rigor/plugin/registry.rb +1 -1
  32. data/lib/rigor/scope.rb +16 -5
  33. data/lib/rigor/version.rb +1 -1
  34. data/lib/rigor.rb +1 -0
  35. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +1 -1
  36. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -1
  37. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -1
  38. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +3 -3
  39. data/sig/rigor/plugin/base.rbs +2 -0
  40. data/sig/rigor/scope.rbs +3 -1
  41. data/skills/rigor-plugin-author/SKILL.md +8 -5
  42. data/skills/rigor-plugin-author/references/02-walker-and-types.md +8 -4
  43. 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 `type_specifier` DSL, wrapped as
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 `type_specifier`, or if
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 `type_specifier`),
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
 
@@ -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 (`type_specifier`), AST node rules (`node_rule`),
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
- # type_specifier methods: [:assert_kind_of] do |call_node, scope|
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
- def type_specifier(methods:, &block)
451
- raise ArgumentError, "Plugin::Base.type_specifier requires a block body" if block.nil?
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.type_specifier methods: must be a non-empty Array of Symbol/String, " \
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 {.type_specifier}. See the CHANGELOG
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 {.type_specifier} rules for a call. The engine
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 `type_specifier` — see the CHANGELOG migration note."
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.2.5"
4
+ VERSION = "0.2.6"
5
5
  end
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 `type_specifier methods:` gate.
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
- type_specifier methods: AssertionAnalyzer::SUPPORTED_METHODS do |call_node, scope|
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
- type_specifier methods: %i[to not_to to_not] do |call_node, scope|
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 `type_specifier` DSL. The
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
- type_specifier methods: [:bind] do |call_node, scope|
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 `type_specifier` body for `T.bind` — same sigil gate as
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).
@@ -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
- `type_specifier(methods:)` to *supply* a return type or narrowing facts
101
- for call sites the core analyzer types as `Dynamic`. `#diagnostics_for_file`
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
- `type_specifier`. See Phase 2.)
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` / `type_specifier`, RBS for the DSL. | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) |
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` / `type_specifier` return-type hooks (`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. |
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` / `type_specifier`
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
- type_specifier methods: [:assert_kind_of] do |call_node, scope|
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` / `type_specifier`) makes a
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
- `type_specifier` and a `sig/` bundle. Verify by eye with `rigor check`;
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.5
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