rigortype 0.1.19 → 0.2.0

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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -23
  3. data/lib/rigor/analysis/check_rules/rule_walk.rb +3 -21
  4. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
  5. data/lib/rigor/analysis/check_rules.rb +492 -71
  6. data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
  7. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
  8. data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
  9. data/lib/rigor/analysis/fact_store.rb +5 -4
  10. data/lib/rigor/analysis/rule_catalog.rb +153 -6
  11. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +17 -17
  12. data/lib/rigor/analysis/runner/project_pre_passes.rb +9 -8
  13. data/lib/rigor/analysis/runner.rb +17 -6
  14. data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
  15. data/lib/rigor/analysis/worker_session.rb +10 -14
  16. data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
  17. data/lib/rigor/cache/store.rb +5 -3
  18. data/lib/rigor/cli/annotate_command.rb +28 -7
  19. data/lib/rigor/cli/baseline_command.rb +4 -3
  20. data/lib/rigor/cli/check_command.rb +115 -16
  21. data/lib/rigor/cli/coverage_command.rb +148 -16
  22. data/lib/rigor/cli/coverage_scan.rb +57 -0
  23. data/lib/rigor/cli/explain_command.rb +2 -0
  24. data/lib/rigor/cli/lsp_command.rb +3 -7
  25. data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
  26. data/lib/rigor/cli/mutation_protection_report.rb +73 -0
  27. data/lib/rigor/cli/options.rb +9 -0
  28. data/lib/rigor/cli/plugins_command.rb +2 -1
  29. data/lib/rigor/cli/protection_renderer.rb +63 -0
  30. data/lib/rigor/cli/protection_report.rb +68 -0
  31. data/lib/rigor/cli/sig_gen_command.rb +2 -1
  32. data/lib/rigor/cli/trace_command.rb +2 -1
  33. data/lib/rigor/cli/triage_command.rb +2 -1
  34. data/lib/rigor/cli/type_of_command.rb +1 -1
  35. data/lib/rigor/cli/type_scan_command.rb +2 -1
  36. data/lib/rigor/cli.rb +3 -2
  37. data/lib/rigor/configuration/dependencies.rb +2 -4
  38. data/lib/rigor/configuration.rb +45 -7
  39. data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
  40. data/lib/rigor/environment/class_registry.rb +4 -3
  41. data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
  42. data/lib/rigor/environment/lockfile_resolver.rb +1 -1
  43. data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
  44. data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
  45. data/lib/rigor/environment/rbs_loader.rb +49 -5
  46. data/lib/rigor/environment.rb +17 -7
  47. data/lib/rigor/flow_contribution/fact.rb +1 -1
  48. data/lib/rigor/flow_contribution.rb +3 -5
  49. data/lib/rigor/inference/acceptance.rb +17 -9
  50. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  51. data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
  52. data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
  53. data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
  54. data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
  55. data/lib/rigor/inference/expression_typer.rb +20 -28
  56. data/lib/rigor/inference/hkt_body.rb +8 -11
  57. data/lib/rigor/inference/hkt_body_parser.rb +10 -12
  58. data/lib/rigor/inference/hkt_registry.rb +10 -11
  59. data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
  60. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +156 -21
  61. data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
  62. data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
  63. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
  64. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
  65. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
  66. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
  67. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
  68. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +90 -15
  69. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
  70. data/lib/rigor/inference/method_dispatcher.rb +40 -48
  71. data/lib/rigor/inference/mutation_widening.rb +5 -11
  72. data/lib/rigor/inference/narrowing.rb +14 -16
  73. data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
  74. data/lib/rigor/inference/project_patched_methods.rb +4 -7
  75. data/lib/rigor/inference/project_patched_scanner.rb +2 -13
  76. data/lib/rigor/inference/protection_scanner.rb +86 -0
  77. data/lib/rigor/inference/scope_indexer.rb +129 -55
  78. data/lib/rigor/inference/statement_evaluator.rb +244 -114
  79. data/lib/rigor/inference/struct_fold_safety.rb +181 -0
  80. data/lib/rigor/inference/synthetic_method.rb +7 -7
  81. data/lib/rigor/language_server/completion_provider.rb +6 -12
  82. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  83. data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
  84. data/lib/rigor/language_server/hover_provider.rb +2 -3
  85. data/lib/rigor/language_server/hover_renderer.rb +2 -11
  86. data/lib/rigor/language_server/server.rb +9 -17
  87. data/lib/rigor/language_server.rb +4 -5
  88. data/lib/rigor/plugin/base.rb +10 -8
  89. data/lib/rigor/plugin/macro/block_as_method.rb +3 -4
  90. data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
  91. data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
  92. data/lib/rigor/plugin/macro.rb +4 -5
  93. data/lib/rigor/plugin/manifest.rb +45 -66
  94. data/lib/rigor/plugin/registry.rb +6 -7
  95. data/lib/rigor/plugin/type_node_resolver.rb +6 -8
  96. data/lib/rigor/protection/mutation_scanner.rb +120 -0
  97. data/lib/rigor/protection/mutator.rb +246 -0
  98. data/lib/rigor/rbs_extended.rb +24 -36
  99. data/lib/rigor/reflection.rb +4 -7
  100. data/lib/rigor/scope/discovery_index.rb +14 -2
  101. data/lib/rigor/scope.rb +54 -11
  102. data/lib/rigor/sig_gen/observed_call.rb +3 -3
  103. data/lib/rigor/sig_gen/writer.rb +40 -2
  104. data/lib/rigor/source/constant_path.rb +62 -0
  105. data/lib/rigor/source.rb +1 -0
  106. data/lib/rigor/type/bound_method.rb +2 -11
  107. data/lib/rigor/type/combinator.rb +16 -3
  108. data/lib/rigor/type/constant.rb +2 -11
  109. data/lib/rigor/type/data_class.rb +2 -11
  110. data/lib/rigor/type/data_instance.rb +2 -11
  111. data/lib/rigor/type/hash_shape.rb +2 -11
  112. data/lib/rigor/type/integer_range.rb +2 -11
  113. data/lib/rigor/type/intersection.rb +2 -11
  114. data/lib/rigor/type/nominal.rb +2 -11
  115. data/lib/rigor/type/plain_lattice.rb +37 -0
  116. data/lib/rigor/type/refined.rb +72 -13
  117. data/lib/rigor/type/singleton.rb +2 -11
  118. data/lib/rigor/type/struct_class.rb +75 -0
  119. data/lib/rigor/type/struct_instance.rb +93 -0
  120. data/lib/rigor/type/tuple.rb +5 -15
  121. data/lib/rigor/type.rb +2 -0
  122. data/lib/rigor/version.rb +1 -1
  123. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
  124. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
  125. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +3 -3
  126. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
  127. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
  128. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +7 -10
  129. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
  130. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
  131. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +6 -8
  132. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
  133. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -2
  134. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
  135. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
  136. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
  137. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
  138. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
  139. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
  140. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
  141. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +7 -9
  142. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
  143. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
  144. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +3 -3
  145. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
  146. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
  147. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
  148. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -1
  149. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
  150. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
  151. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +5 -5
  152. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
  153. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
  154. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  155. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +19 -14
  156. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
  157. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
  158. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
  159. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
  160. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
  161. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
  162. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
  163. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +28 -41
  164. data/sig/rigor/scope.rbs +9 -1
  165. data/sig/rigor/type.rbs +36 -1
  166. metadata +19 -1
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../source/constant_path"
6
+
7
+ module Rigor
8
+ module Inference
9
+ # ADR-48 Struct follow-up, slice 3 — the fold-safe-local scan. Determines,
10
+ # for a single local-variable scope (a method body or the program
11
+ # top-level), which `Struct`-materialised locals are **provably never
12
+ # mutated, aliased, or escaped** and so may have their member reads folded
13
+ # off a *stored* binding (relaxing the slice-2 fresh-receiver gate).
14
+ #
15
+ # The analysis is a conservative ALLOW-LIST, not a deny-list: a local is
16
+ # fold-safe only when *every* read of it is the receiver of a known-pure
17
+ # read call. Anything the scan does not recognise as a pure read — a
18
+ # setter, an `[]=` / operator-write, an argument / alias / container store
19
+ # / return (escape), or an unknown method call (which could mutate `self`
20
+ # internally) — disqualifies the local. A missed case therefore makes the
21
+ # scan over-conservative (no fold), **never unsound** (folding a mutated
22
+ # value) — the false-positive-safe direction.
23
+ #
24
+ # Soundness rests on a counting identity: a local `n` is fold-safe iff
25
+ # *every* `LocalVariableReadNode(n)` is the receiver of a pure-read call.
26
+ # Equivalently `total_reads(n) == pure_receiver_reads(n)`. Any other
27
+ # occurrence — `n` as a setter receiver (`n.x = v` is a `:x=` call, not a
28
+ # pure read), an `[]=`/operator-write receiver (the receiver read is not
29
+ # under a pure call), a call argument, an assignment RHS (alias), a
30
+ # container element, a bare value (return/escape) — leaves a read that is
31
+ # not a pure-receiver read, so the counts diverge.
32
+ #
33
+ # See docs/notes/20260615-struct-folding-slice3-design.md and
34
+ # docs/adr/48-data-struct-value-folding.md § "Struct follow-up".
35
+ module StructFoldSafety
36
+ module_function
37
+
38
+ EMPTY = Set.new.freeze
39
+
40
+ # The fixed `Struct` read methods that never mutate. A member-reader name
41
+ # (`:x`) is added per-local from the local's recorded layout. A setter
42
+ # (`:x=`), `:[]=`, `store`, `push`, etc. are deliberately absent.
43
+ FIXED_READS = %i[
44
+ [] dig to_h to_hash to_a values members deconstruct deconstruct_keys
45
+ == != eql? equal? hash inspect to_s size length frozen? each each_pair
46
+ values_at with
47
+ ].to_set.freeze
48
+
49
+ # Nested `def` / `class` / `module` bodies open a *new* local-variable
50
+ # scope, so the scan does not descend into them — a local of the same
51
+ # name there is a different binding. Blocks share the enclosing locals
52
+ # (closures), so the scan does descend into them.
53
+ def scope_boundary?(node)
54
+ node.is_a?(Prism::DefNode) || node.is_a?(Prism::ClassNode) ||
55
+ node.is_a?(Prism::ModuleNode) || node.is_a?(Prism::SingletonClassNode)
56
+ end
57
+
58
+ # @param root [Prism::Node, nil] the local-variable scope to scan.
59
+ # @param layout_lookup [#call] a `String -> Array[Symbol] | nil` resolver
60
+ # mapping a constant receiver name to its struct member list.
61
+ # @return [Set<Symbol>] the fold-safe local names.
62
+ def fold_safe_locals(root, layout_lookup)
63
+ return EMPTY if root.nil?
64
+
65
+ members = {}
66
+ writes = Hash.new(0)
67
+ collect_struct_locals(root, layout_lookup, members, writes)
68
+ return EMPTY if members.empty?
69
+
70
+ total = Hash.new(0)
71
+ pure = Hash.new(0)
72
+ count_uses(root, members, total, pure)
73
+
74
+ safe = members.each_key.select do |name|
75
+ writes[name] == 1 && total[name].positive? && total[name] == pure[name]
76
+ end
77
+ safe.empty? ? EMPTY : safe.to_set
78
+ end
79
+
80
+ # Pass 1 — record each local's single struct materialisation (its member
81
+ # set) and count its assignments. A local assigned more than once is
82
+ # later excluded (the static fold-safe set cannot track a rebinding).
83
+ def collect_struct_locals(node, layout_lookup, members, writes)
84
+ return if node.nil?
85
+
86
+ if node.is_a?(Prism::LocalVariableWriteNode)
87
+ writes[node.name] += 1
88
+ found = struct_materialization_members(node.value, layout_lookup)
89
+ members[node.name] = found if found
90
+ end
91
+
92
+ each_local_scope_child(node) do |child|
93
+ collect_struct_locals(child, layout_lookup, members, writes)
94
+ end
95
+ end
96
+
97
+ # Pass 2 — count, per recorded struct local, total reads vs. reads that
98
+ # are the receiver of a pure-read call.
99
+ def count_uses(node, members, total, pure)
100
+ return if node.nil?
101
+
102
+ total[node.name] += 1 if node.is_a?(Prism::LocalVariableReadNode) && members.key?(node.name)
103
+
104
+ if node.is_a?(Prism::CallNode)
105
+ receiver = node.receiver
106
+ if receiver.is_a?(Prism::LocalVariableReadNode) && members.key?(receiver.name) &&
107
+ pure_read_call?(node, members[receiver.name])
108
+ pure[receiver.name] += 1
109
+ end
110
+ end
111
+
112
+ each_local_scope_child(node) do |child|
113
+ count_uses(child, members, total, pure)
114
+ end
115
+ end
116
+
117
+ # A call is a pure read of the receiver when its name is a fixed Struct
118
+ # read or one of the receiver's member readers. Setters (`:x=`), `:[]=`,
119
+ # and any unknown method are excluded.
120
+ def pure_read_call?(call_node, member_set)
121
+ name = call_node.name
122
+ FIXED_READS.include?(name) || member_set.include?(name)
123
+ end
124
+
125
+ # The member set of a `<Struct chain>.new(...)` / `.[]` materialisation,
126
+ # or nil. Handles the inline `Struct.new(:a, :b).new(...)` form and the
127
+ # `Const.new(...)` form (resolved through the layout side-table).
128
+ def struct_materialization_members(value_node, layout_lookup)
129
+ return nil unless value_node.is_a?(Prism::CallNode)
130
+ return nil unless %i[new []].include?(value_node.name)
131
+
132
+ receiver = value_node.receiver
133
+ case receiver
134
+ when Prism::CallNode
135
+ struct_new_member_set(receiver)
136
+
137
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
138
+ name = Source::ConstantPath.qualified_name_or_nil(receiver)
139
+ name && member_set_of(layout_lookup.call(name))
140
+ end
141
+ end
142
+
143
+ # The Symbol member set of a literal `Struct.new(:a, :b [, keyword_init:])`
144
+ # call, or nil. (A leading String name and the trailing options hash are
145
+ # ignored — only the literal-Symbol positionals contribute.)
146
+ def struct_new_member_set(call_node)
147
+ return nil unless call_node.is_a?(Prism::CallNode) && call_node.name == :new
148
+ return nil unless meta_constant?(call_node.receiver, :Struct)
149
+
150
+ args = call_node.arguments&.arguments || []
151
+ positional = args.last.is_a?(Prism::KeywordHashNode) ? args[0..-2] : args
152
+ positional = positional[1..] if positional.first.is_a?(Prism::StringNode)
153
+ return nil if positional.nil? || positional.empty?
154
+ return nil unless positional.all?(Prism::SymbolNode)
155
+
156
+ positional.to_set { |sym| sym.unescaped.to_sym }
157
+ end
158
+
159
+ def member_set_of(members)
160
+ members && !members.empty? ? members.to_set : nil
161
+ end
162
+
163
+ def meta_constant?(node, name)
164
+ case node
165
+ when Prism::ConstantReadNode then node.name == name
166
+ when Prism::ConstantPathNode then node.parent.nil? && node.name == name
167
+ end
168
+ end
169
+
170
+ # Yields each child to recurse into, skipping the subtree of a nested
171
+ # local-variable-scope boundary (a `def` / `class` / `module`).
172
+ def each_local_scope_child(node)
173
+ node.compact_child_nodes.each do |child|
174
+ next if scope_boundary?(child)
175
+
176
+ yield child
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -8,13 +8,13 @@ module Rigor
8
8
  # the template name. Stored in {SyntheticMethodIndex} and
9
9
  # consulted by {MethodDispatcher} below the RBS dispatch tier.
10
10
  #
11
- # Per ADR-16 § WD13 (cost-bounded best-effort): the v0.1.x
12
- # delivery commitment is the floor method names emit; their
13
- # return types degrade to `Dynamic[T]` until slice 6
14
- # (precision promotion) routes the recorded `return_type`
15
- # string through ADR-13's `Plugin::TypeNodeResolver` chain.
16
- # The string is preserved so the ceiling slice can resolve it
17
- # without re-walking.
11
+ # Per ADR-16 § WD13 (cost-bounded best-effort): method names
12
+ # emit with return types promoted by the dispatcher's Slice 6
13
+ # tiers Slice 6a promotes via `origin_module` (TierB) and
14
+ # Slice 6b via `return_type` nominal lookup (TierC). The
15
+ # `return_type` string is preserved so promotion can resolve
16
+ # it without re-walking; `"untyped"` / `"void"` placeholders
17
+ # fall back to `Dynamic[Top]`.
18
18
  #
19
19
  # The `provenance` Hash carries debug / `--explain` metadata:
20
20
  # plugin id, the template's call shape, and the source
@@ -21,20 +21,14 @@ require_relative "../type/hash_shape"
21
21
 
22
22
  module Rigor
23
23
  module LanguageServer
24
- # Answers `textDocument/completion` requests. v1 (slice 5)
25
- # ships method completion for `obj.|`: when the cursor sits on
26
- # a `CallNode` with a known-type receiver, the provider
27
- # enumerates the receiver's RBS-known methods and returns each
28
- # as an LSP `CompletionItem`.
29
- #
30
- # Constant-path completion (slice 6), Union / Intersection /
31
- # Refined / Shape receiver handling (slice 7), and parse-recovery
32
- # fallback for malformed buffers (slice 8) extend this v1 floor.
24
+ # Answers `textDocument/completion` requests. Provides method
25
+ # completion for `obj.|` (known-type receiver RBS-known methods),
26
+ # constant-path completion, hash-key completion, Union / Intersection /
27
+ # Refined / Shape receiver handling, and parse-recovery fallback for
28
+ # malformed buffers (sentinel injection).
33
29
  #
34
30
  # LSP `CompletionItemKind` values used:
35
- # - 2 = Method
36
- #
37
- # Slice 6 will add 7 (Class), 9 (Module), 21 (Constant).
31
+ # - 2 = Method, 5 = Field, 7 = Class, 9 = Module, 21 = Constant.
38
32
  class CompletionProvider # rubocop:disable Metrics/ClassLength
39
33
  include BufferResolution
40
34
 
@@ -15,10 +15,10 @@ module Rigor
15
15
  # a `BufferBinding` from the BufferTable entry, runs the Runner,
16
16
  # and pushes the resulting LSP `Diagnostic[]` through the writer.
17
17
  #
18
- # Slice 4 is synchronous: each call blocks until analysis
19
- # completes (typically 100-300ms warm). Debouncing (200ms
20
- # quiet-time before publish) and Ractor-pool dispatch are
21
- # queued for slice 4b / slice 8 respectively.
18
+ # Debouncing is wired via an optional `Debouncer` injected at
19
+ # construction (delay defaults to 200ms quiet-time); without a
20
+ # debouncer each call blocks synchronously (primarily for specs).
21
+ # Ractor-pool dispatch is queued.
22
22
  class DiagnosticPublisher
23
23
  # Maps Rigor severity symbols to LSP DiagnosticSeverity
24
24
  # integers per spec § "Diagnostic":
@@ -128,9 +128,9 @@ module Rigor
128
128
  end
129
129
 
130
130
  # LSP `Range` is 0-based start + end with `character` in
131
- # UTF-16 code units. Slice 6 emits byte columns (correct for
132
- # ASCII source); UTF-16 conversion stays queued per design
133
- # doc § "Open questions".
131
+ # UTF-16 code units. This implementation emits byte columns
132
+ # (correct for ASCII source); UTF-16 conversion stays queued
133
+ # per design doc § "Open questions".
134
134
  def range_from(location)
135
135
  {
136
136
  start: { line: location.start_line - 1, character: location.start_column },
@@ -64,9 +64,8 @@ module Rigor
64
64
  end
65
65
 
66
66
  def base_scope(_path)
67
- # Slice 7: pull the Environment from the cached
68
- # ProjectContext so hovers don't pay the RBS-load tax on
69
- # every cursor stop.
67
+ # Pulls the Environment from the cached ProjectContext so
68
+ # each hover avoids repeating the RBS-load build.
70
69
  Scope.empty(environment: @project_context.environment)
71
70
  end
72
71
  end
@@ -11,17 +11,8 @@ module Rigor
11
11
  module LanguageServer
12
12
  # Builds the LSP `Hover.contents` markdown body. Dispatches on
13
13
  # the hovered Prism node class so each shape (method call,
14
- # constant, local, literal, ) gets the most relevant
15
- # type-aware presentation.
16
- #
17
- # Slice A1 (this commit) ships:
18
- # - default body bit-for-bit matching the LSP v1 slice 5
19
- # output (`type:` / `erased:` / `node:`),
20
- # - `Prism::CallNode` specialisation surfacing the receiver
21
- # type + RBS-erased method signature.
22
- #
23
- # Slices A2-A4 extend the dispatch with constant /
24
- # local / ivar / literal renderers per
14
+ # constant, local, ivar, literal) gets the most relevant
15
+ # type-aware presentation per
25
16
  # `docs/design/20260517-lsp-hover-completion.md`.
26
17
  class HoverRenderer
27
18
  # @param node_scope_lookup [#[]] node-to-scope table built
@@ -6,21 +6,13 @@ require_relative "buffer_table"
6
6
  module Rigor
7
7
  module LanguageServer
8
8
  # LSP server lifecycle state machine + JSON-RPC method dispatcher.
9
- #
10
- # Slice 1 (this commit) ships:
11
- # - State machine: `:uninitialized` `:initialized` `:shutdown`
12
- # `:exited`.
13
- # - Three lifecycle handlers: `initialize`, `shutdown`, `exit`.
14
- # - {#dispatch} which routes (method, params) to the matching
15
- # handler and returns the response payload (or `nil` for
16
- # notifications). Out-of-state requests return the
17
- # spec-defined `InvalidRequest` (-32002) / `MethodNotFound`
18
- # (-32601) error shapes.
19
- #
20
- # Slice 2 wraps this dispatcher in a stdio JSON-RPC reader /
21
- # writer so the CLI subcommand can serve real LSP clients.
22
- # Slice 3+ adds document sync; slice 4+ adds publishDiagnostics;
23
- # slice 5-8 add the rest of the v1 capability surface.
9
+ # State machine: `:uninitialized` → `:initialized` → `:shutdown`
10
+ # `:exited`. {#dispatch} routes (method, params) to the matching
11
+ # handler and returns the response payload (`nil` for notifications);
12
+ # out-of-state requests return `InvalidRequest` (-32002) /
13
+ # `MethodNotFound` (-32601). Full v1 capability surface (document
14
+ # sync, publishDiagnostics, hover, completion, sig-help, folding,
15
+ # selection, and watched-file invalidation) is implemented.
24
16
  class Server # rubocop:disable Metrics/ClassLength
25
17
  # JSON-RPC error codes per LSP spec § "Response Message".
26
18
  ERROR_PARSE_ERROR = -32_700
@@ -58,8 +50,8 @@ module Rigor
58
50
  # the providers read on every request. When present,
59
51
  # `workspace/didChangeWatchedFiles` and
60
52
  # `workspace/didChangeConfiguration` invalidate the cache;
61
- # nil means "no project context", which is the slice 1-6
62
- # behaviour (each request rebuilds env from scratch).
53
+ # nil means "no project context": each request rebuilds env
54
+ # from scratch (mainly for specs and backward compatibility).
63
55
  def initialize(buffer_table: BufferTable.new, publisher: nil, # rubocop:disable Metrics/ParameterLists
64
56
  hover_provider: nil, document_symbol_provider: nil,
65
57
  completion_provider: nil, signature_help_provider: nil,
@@ -3,11 +3,10 @@
3
3
  module Rigor
4
4
  # The Language Server subsystem. See
5
5
  # `docs/design/20260517-language-server.md` for the design.
6
- # Slice 1 ships the namespace + a minimal {Server} lifecycle the
7
- # `rigor lsp` CLI subcommand can drive. Later slices add the
8
- # stdio JSON-RPC transport (slice 2), the BufferTable (slice 3),
9
- # `publishDiagnostics` (slice 4), and the rest of the v1 capability
10
- # surface.
6
+ # The full v1 capability surface (document sync, publishDiagnostics,
7
+ # hover, completion, sig-help, folding, selection, and watched-file
8
+ # invalidation) is implemented. This module is the namespace and
9
+ # require entry point for the subsystem.
11
10
  module LanguageServer
12
11
  end
13
12
  end
@@ -18,10 +18,11 @@ module Rigor
18
18
  # overrides {#init} to wire up any state it needs from the
19
19
  # injected service container.
20
20
  #
21
- # Slice 1 ships only the registration / loading plumbing. The
22
- # protocol hooks (dynamic-return contributions, type-specifying
23
- # contributions, dynamic reflection) land in subsequent v0.1.0
24
- # slices and arrive as additional methods on this class.
21
+ # This class implements all plugin protocol hooks: per-call
22
+ # return-type contributions (`dynamic_return`), narrowing-fact
23
+ # contributions (`type_specifier`), AST node rules (`node_rule`),
24
+ # and producer/cache hooks. Cumulative implementation per the
25
+ # ADR-37 / ADR-52 slice chain.
25
26
  #
26
27
  # Example plugin:
27
28
  #
@@ -543,10 +544,11 @@ module Rigor
543
544
  #
544
545
  # `path` is the analysed file path; `scope` is the entry
545
546
  # `Rigor::Scope` after `ScopeIndexer` ran; `root` is the
546
- # parsed `Prism::Node` root. Plugin authors traverse `root`
547
- # themselves if they need node-scoped rules the
548
- # `Rule<TNode>` API ADR-2 § "Custom rules" mentions stays
549
- # deferred to v0.1.x.
547
+ # parsed `Prism::Node` root. Plugin authors can traverse
548
+ # `root` themselves or declare rules via the `.node_rule` DSL
549
+ # (ADR-37, shipped). The PHPStan-style `Rule<TNode>` base
550
+ # class mentioned in ADR-2 was superseded by the block-based
551
+ # `.node_rule` DSL.
550
552
  #
551
553
  # Default returns `[]` so plugins that contribute through
552
554
  # other channels (e.g. slice-4 narrowing contributions,
@@ -29,10 +29,9 @@ module Rigor
29
29
  # class-level methods whose block argument runs as if it were
30
30
  # an instance method of the receiver.
31
31
  #
32
- # Slice 1a (this file) is **the contract only**. The engine
33
- # hook that consults registered entries and narrows
34
- # `Scope#self_type` for a block whose enclosing call matches
35
- # arrives in slice 1b.
32
+ # Engine wiring: `Inference::MacroBlockSelfType.narrow_self_type_for`
33
+ # (called from expression_typer.rb) consults registered entries
34
+ # and narrows `Scope#self_type` for matching block call sites.
36
35
  #
37
36
  # ## Fields
38
37
  #
@@ -68,13 +68,10 @@ module Rigor
68
68
  # to a later slice — the `returns:` declarations cost
69
69
  # nothing to write today and unlock precision then.
70
70
  #
71
- # ## Slice 2a scope
72
- #
73
- # This file ships the value class only. Slice 2b wires the
74
- # pre-pass that scans Tier C call sites + the
75
- # `SyntheticMethodIndex` the dispatcher consults; slice 2c
76
- # authors `plugins/rigor-dry-struct/` and
77
- # `plugins/rigor-dry-types/` as the worked consumers.
71
+ # Engine wiring: `Inference::SyntheticMethodScanner` (slice 2b,
72
+ # `synthetic_method_scanner.rb`) consumes `manifest.heredoc_templates`.
73
+ # Worked consumers: `plugins/rigor-dry-struct/` and
74
+ # `plugins/rigor-dry-types/` (slice 2c).
78
75
  class HeredocTemplate
79
76
  NAME_PLACEHOLDER = "\#{name}"
80
77
 
@@ -78,12 +78,9 @@ module Rigor
78
78
  # facts (attr_reader / after_save / etc.); slice 3 emits
79
79
  # only the module's plain instance methods.
80
80
  #
81
- # ## Slice 3a scope
82
- #
83
- # This file ships the value class only. Slice 3b wires the
84
- # scanner that walks Tier B call sites + the per-method
85
- # explosion via `SyntheticMethodIndex`; slice 3c authors
86
- # `plugins/rigor-devise/` model side as the worked consumer.
81
+ # Engine wiring: `Inference::SyntheticMethodScanner#collect_trait_registries`
82
+ # (slice 3b) walks Tier B call sites and explodes per-method
83
+ # entries. Worked consumer: `plugins/rigor-devise/` (slice 3c).
87
84
  class TraitRegistry
88
85
  REST_POSITION = :rest
89
86
 
@@ -14,11 +14,10 @@ module Rigor
14
14
  # `nested_class_templates:`) and the substrate consumes them
15
15
  # to recognise the call shapes a library exposes to its users.
16
16
  #
17
- # Slice 1a (this file's first delivery) ships the Tier A value
18
- # class only. The other tiers' value classes + their manifest
19
- # slots arrive in subsequent slices per ADR-16 § Implementation
20
- # slicing. The namespace is reserved here so subsequent slices
21
- # add files alongside `block_as_method.rb` without churn.
17
+ # Tier A (`BlockAsMethod`), Tier B (`TraitRegistry`), Tier C
18
+ # (`HeredocTemplate`), and ADR-36 nested-class emission
19
+ # (`NestedClassTemplate`) value classes are all shipped here.
20
+ # Engine wiring lives in `lib/rigor/inference/`.
22
21
  #
23
22
  # Per ADR-16 § WD13, substrate-produced output ships at a
24
23
  # **floor** in v0.1.x ("substrate-affected code parses cleanly