rigortype 0.3.9 → 0.4.1

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 (407) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -4
  3. data/data/core_overlay/enumerable.rbs +51 -0
  4. data/data/core_overlay/enumerator.rbs +84 -0
  5. data/data/core_overlay/hash_rbs3.rbs +41 -0
  6. data/data/core_overlay/process.rbs +40 -0
  7. data/data/core_overlay/string_io.rbs +33 -0
  8. data/data/effects/core.yml +3 -3
  9. data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
  10. data/docs/handbook/03-narrowing.md +95 -10
  11. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  12. data/docs/handbook/07-rbs-and-extended.md +16 -11
  13. data/docs/handbook/08-understanding-errors.md +3 -2
  14. data/docs/handbook/10-sorbet.md +9 -10
  15. data/docs/handbook/11-sig-gen.md +454 -13
  16. data/docs/install.md +87 -15
  17. data/docs/manual/02-cli-reference.md +65 -4
  18. data/docs/manual/03-configuration.md +12 -0
  19. data/docs/manual/04-diagnostics.md +4 -0
  20. data/docs/manual/07-plugins.md +1 -1
  21. data/docs/manual/08-skills.md +4 -3
  22. data/docs/manual/10-mcp-server.md +3 -2
  23. data/docs/manual/12-caching.md +43 -4
  24. data/docs/manual/14-rails-quickstart.md +19 -11
  25. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  26. data/docs/manual/19-effect-labels.md +10 -0
  27. data/docs/manual/plugins/README.md +7 -0
  28. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  29. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  30. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  31. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  32. data/docs/manual/plugins/rigor-activejob.md +7 -0
  33. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  34. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  35. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  36. data/docs/manual/plugins/rigor-grape.md +106 -0
  37. data/docs/manual/plugins/rigor-graphql.md +23 -2
  38. data/docs/manual/plugins/rigor-pundit.md +8 -1
  39. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  40. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  41. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  42. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  43. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  44. data/exe/rigor +19 -4
  45. data/lib/rigor/analysis/baseline.rb +1 -1
  46. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  47. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  48. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  49. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  50. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  51. data/lib/rigor/analysis/check_rules/source_arity.rb +427 -0
  52. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  53. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  54. data/lib/rigor/analysis/check_rules.rb +638 -136
  55. data/lib/rigor/analysis/dependency_recorder.rb +222 -17
  56. data/lib/rigor/analysis/fact_store.rb +9 -0
  57. data/lib/rigor/analysis/incremental.rb +26 -0
  58. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
  59. data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
  60. data/lib/rigor/analysis/incremental_session.rb +309 -53
  61. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
  62. data/lib/rigor/analysis/project_scan.rb +11 -1
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -1
  65. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  66. data/lib/rigor/analysis/run_cache_key.rb +46 -18
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
  69. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  70. data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
  71. data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
  72. data/lib/rigor/analysis/runner.rb +603 -54
  73. data/lib/rigor/analysis/shadow_harness.rb +27 -0
  74. data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
  75. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  76. data/lib/rigor/analysis/template_unit_paths.rb +93 -0
  77. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  78. data/lib/rigor/analysis/template_units.rb +409 -0
  79. data/lib/rigor/analysis/worker_session.rb +57 -13
  80. data/lib/rigor/bleeding_edge.rb +0 -2
  81. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  82. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  83. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  84. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  85. data/lib/rigor/cache/descriptor.rb +45 -1
  86. data/lib/rigor/cache/engine_source.rb +29 -1
  87. data/lib/rigor/cache/file_digest.rb +28 -0
  88. data/lib/rigor/cache/incremental_snapshot.rb +102 -8
  89. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  90. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  91. data/lib/rigor/cache/store.rb +63 -4
  92. data/lib/rigor/cache/value_digest.rb +325 -0
  93. data/lib/rigor/ci_detector.rb +1 -0
  94. data/lib/rigor/cli/check_command.rb +83 -13
  95. data/lib/rigor/cli/doc_links.rb +1 -1
  96. data/lib/rigor/cli/docs_command.rb +4 -4
  97. data/lib/rigor/cli/doctor_command.rb +1 -1
  98. data/lib/rigor/cli/effects_report.rb +2 -2
  99. data/lib/rigor/cli/plugin_command.rb +3 -3
  100. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  101. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  102. data/lib/rigor/cli/skill_command.rb +1 -1
  103. data/lib/rigor/cli/skill_describe.rb +0 -1
  104. data/lib/rigor/cli/type_of_command.rb +19 -5
  105. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  106. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  107. data/lib/rigor/cli/unused_command.rb +3 -3
  108. data/lib/rigor/cli.rb +21 -3
  109. data/lib/rigor/config_audit.rb +3 -2
  110. data/lib/rigor/config_path.rb +29 -0
  111. data/lib/rigor/configuration/severity_profile.rb +24 -3
  112. data/lib/rigor/configuration.rb +71 -7
  113. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  114. data/lib/rigor/effects/attribution.rb +11 -2
  115. data/lib/rigor/effects/callee_rule.rb +368 -0
  116. data/lib/rigor/effects/catalog.rb +7 -4
  117. data/lib/rigor/effects/collector.rb +11 -5
  118. data/lib/rigor/effects/config_envelopes.rb +9 -2
  119. data/lib/rigor/effects/definition_context.rb +179 -0
  120. data/lib/rigor/effects/effect_table.rb +11 -3
  121. data/lib/rigor/effects/envelope_check.rb +1 -1
  122. data/lib/rigor/effects/envelope_index.rb +15 -0
  123. data/lib/rigor/effects/file_collection.rb +59 -5
  124. data/lib/rigor/effects/framework_units.rb +1 -1
  125. data/lib/rigor/effects/identity.rb +16 -0
  126. data/lib/rigor/effects/local_ownership.rb +38 -12
  127. data/lib/rigor/effects/method_key.rb +21 -0
  128. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  129. data/lib/rigor/effects/plugin_facts.rb +49 -37
  130. data/lib/rigor/effects/propagator.rb +295 -16
  131. data/lib/rigor/effects/registry.rb +1 -1
  132. data/lib/rigor/effects/scanner.rb +121 -72
  133. data/lib/rigor/effects/signature_sources.rb +1 -1
  134. data/lib/rigor/effects/snapshot.rb +2 -1
  135. data/lib/rigor/effects/summary.rb +27 -4
  136. data/lib/rigor/effects/unit_scan.rb +385 -30
  137. data/lib/rigor/effects/visibility.rb +101 -0
  138. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
  139. data/lib/rigor/environment/lockfile_resolver.rb +27 -1
  140. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  141. data/lib/rigor/environment/member_consistency.rb +298 -0
  142. data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
  143. data/lib/rigor/environment/rbs_loader.rb +400 -134
  144. data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
  145. data/lib/rigor/environment.rb +104 -96
  146. data/lib/rigor/hashing/xxh3.rb +264 -0
  147. data/lib/rigor/inference/acceptance.rb +139 -9
  148. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  149. data/lib/rigor/inference/block_call_timing.rb +338 -0
  150. data/lib/rigor/inference/block_parameter_binder.rb +88 -26
  151. data/lib/rigor/inference/block_repetition.rb +71 -0
  152. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  153. data/lib/rigor/inference/budget_trace.rb +2 -1
  154. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  155. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  156. data/lib/rigor/inference/captured_locals.rb +393 -15
  157. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  158. data/lib/rigor/inference/content_join.rb +200 -27
  159. data/lib/rigor/inference/declaration_walk/context.rb +195 -0
  160. data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
  161. data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
  162. data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
  163. data/lib/rigor/inference/declaration_walk.rb +7 -0
  164. data/lib/rigor/inference/def_node_resolver.rb +3 -2
  165. data/lib/rigor/inference/def_return_typer.rb +11 -7
  166. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  167. data/lib/rigor/inference/definer_resolution.rb +266 -0
  168. data/lib/rigor/inference/element_read_widening.rb +22 -10
  169. data/lib/rigor/inference/error_info.rb +196 -0
  170. data/lib/rigor/inference/expression_typer.rb +1665 -538
  171. data/lib/rigor/inference/external_ancestor_resolution.rb +264 -0
  172. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  173. data/lib/rigor/inference/global_write_census.rb +239 -0
  174. data/lib/rigor/inference/guard_rebinding.rb +488 -0
  175. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  176. data/lib/rigor/inference/index_write_widening.rb +16 -3
  177. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  178. data/lib/rigor/inference/jump_targets.rb +82 -0
  179. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  180. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  181. data/lib/rigor/inference/last_line.rb +340 -0
  182. data/lib/rigor/inference/last_status.rb +144 -0
  183. data/lib/rigor/inference/macro_block_self_type.rb +179 -17
  184. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  185. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  186. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  187. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  188. data/lib/rigor/inference/match_rebinding.rb +392 -0
  189. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  190. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  191. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  192. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  193. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  194. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  195. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  196. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  197. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  198. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +901 -38
  199. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  200. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  201. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  203. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  204. data/lib/rigor/inference/module_function_state.rb +213 -0
  205. data/lib/rigor/inference/multi_target_binder.rb +345 -52
  206. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  207. data/lib/rigor/inference/mutation_widening.rb +70 -48
  208. data/lib/rigor/inference/narrowing.rb +674 -124
  209. data/lib/rigor/inference/operand_effects.rb +167 -0
  210. data/lib/rigor/inference/operand_walk.rb +88 -0
  211. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  212. data/lib/rigor/inference/parameter_inference_collector.rb +13 -11
  213. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  214. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  215. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  216. data/lib/rigor/inference/receiver_alias.rb +90 -1
  217. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  218. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  219. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  220. data/lib/rigor/inference/return_barrier.rb +54 -0
  221. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  222. data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
  223. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
  224. data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
  225. data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
  226. data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
  227. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
  228. data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
  229. data/lib/rigor/inference/scope_indexer.rb +5913 -701
  230. data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
  231. data/lib/rigor/inference/statement_evaluator.rb +3249 -497
  232. data/lib/rigor/inference/stored_block_call.rb +54 -0
  233. data/lib/rigor/inference/string_mutation.rb +44 -7
  234. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  235. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  236. data/lib/rigor/language_server/debouncer.rb +0 -1
  237. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  238. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  239. data/lib/rigor/language_server/project_context.rb +5 -3
  240. data/lib/rigor/mcp/server.rb +2 -1
  241. data/lib/rigor/plugin/base.rb +196 -13
  242. data/lib/rigor/plugin/box_probe.rb +91 -0
  243. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  244. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  245. data/lib/rigor/plugin/io_boundary.rb +82 -4
  246. data/lib/rigor/plugin/isolation.rb +1 -1
  247. data/lib/rigor/plugin/loader.rb +2 -1
  248. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  249. data/lib/rigor/plugin/manifest.rb +71 -10
  250. data/lib/rigor/plugin/registry.rb +35 -1
  251. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  252. data/lib/rigor/plugin/template_unit.rb +196 -0
  253. data/lib/rigor/plugin/trust_policy.rb +3 -3
  254. data/lib/rigor/plugin.rb +1 -0
  255. data/lib/rigor/protection/discovery_seed.rb +20 -9
  256. data/lib/rigor/protection/kill_signature.rb +0 -1
  257. data/lib/rigor/protection/mutation_cache.rb +4 -5
  258. data/lib/rigor/rbs_extended.rb +27 -0
  259. data/lib/rigor/reflection/constant_ancestors.rb +146 -0
  260. data/lib/rigor/reflection/constant_path.rb +41 -13
  261. data/lib/rigor/reflection.rb +73 -107
  262. data/lib/rigor/scope/discovery_index.rb +351 -2
  263. data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
  264. data/lib/rigor/scope/resolution_chain.rb +884 -0
  265. data/lib/rigor/scope.rb +981 -198
  266. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  267. data/lib/rigor/sig_gen/classification.rb +22 -5
  268. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  269. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  270. data/lib/rigor/sig_gen/generator.rb +580 -47
  271. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  272. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  273. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  274. data/lib/rigor/sig_gen/renderer.rb +124 -9
  275. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  276. data/lib/rigor/sig_gen/write_result.rb +19 -3
  277. data/lib/rigor/sig_gen/writer.rb +166 -35
  278. data/lib/rigor/sig_gen.rb +3 -0
  279. data/lib/rigor/signature_path_audit.rb +1 -1
  280. data/lib/rigor/source/node_walker.rb +0 -3
  281. data/lib/rigor/source/parameter_envelope.rb +72 -0
  282. data/lib/rigor/source.rb +1 -0
  283. data/lib/rigor/type/combinator.rb +112 -12
  284. data/lib/rigor/type/difference.rb +1 -0
  285. data/lib/rigor/type/hash_shape.rb +1 -1
  286. data/lib/rigor/type/refined.rb +1 -0
  287. data/lib/rigor/version.rb +1 -1
  288. data/lib/rigor.rb +1 -0
  289. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  290. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  291. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  292. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  293. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  294. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  295. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  296. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  297. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  298. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  299. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  300. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  301. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  302. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  303. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  304. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  305. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  306. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  307. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  308. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  309. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  310. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  311. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  312. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  313. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  314. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  315. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  316. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  317. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  318. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  319. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  320. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +10 -0
  321. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  322. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  323. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  324. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  325. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  326. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  327. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  328. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  329. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  330. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  331. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  332. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  333. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  334. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  335. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  336. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  337. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  338. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  339. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  340. data/sig/rigor/analysis/baseline.rbs +69 -7
  341. data/sig/rigor/analysis/fact_store.rbs +1 -1
  342. data/sig/rigor/analysis/project_scan.rbs +74 -0
  343. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  344. data/sig/rigor/effects/effect_table.rbs +60 -0
  345. data/sig/rigor/effects/envelope.rbs +97 -0
  346. data/sig/rigor/effects/envelope_index.rbs +33 -0
  347. data/sig/rigor/effects/file_collection.rbs +81 -0
  348. data/sig/rigor/effects/label.rbs +26 -0
  349. data/sig/rigor/effects/label_set.rbs +46 -0
  350. data/sig/rigor/effects/method_key.rbs +24 -0
  351. data/sig/rigor/effects/origin.rbs +50 -0
  352. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  353. data/sig/rigor/effects/registry.rbs +68 -0
  354. data/sig/rigor/effects/summary.rbs +50 -0
  355. data/sig/rigor/effects/taint_cause.rbs +12 -0
  356. data/sig/rigor/environment.rbs +14 -10
  357. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  358. data/sig/rigor/inference.rbs +6 -4
  359. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  360. data/sig/rigor/plugin/base.rbs +30 -7
  361. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  362. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  363. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  364. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  365. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  366. data/sig/rigor/plugin/loader.rbs +3 -3
  367. data/sig/rigor/plugin/manifest.rbs +50 -11
  368. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  369. data/sig/rigor/plugin/registry.rbs +62 -1
  370. data/sig/rigor/plugin.rbs +1 -1
  371. data/sig/rigor/rbs_extended.rbs +1 -1
  372. data/sig/rigor/reflection.rbs +7 -6
  373. data/sig/rigor/scope.rbs +177 -20
  374. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  375. data/sig/rigor/source.rbs +4 -4
  376. data/sig/rigor/testing.rbs +10 -4
  377. data/sig/rigor/type.rbs +6 -0
  378. data/sig/rigor.rbs +38 -20
  379. data/skills/rigor-ask/SKILL.md +8 -5
  380. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  381. data/skills/rigor-ci-setup/SKILL.md +17 -21
  382. data/skills/rigor-doctor/SKILL.md +24 -22
  383. data/skills/rigor-doctor/references/01-checks.md +97 -33
  384. data/skills/rigor-editor-setup/SKILL.md +6 -4
  385. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  386. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  387. data/skills/rigor-next-steps/SKILL.md +4 -2
  388. data/skills/rigor-plugin-author/SKILL.md +19 -23
  389. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  390. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  391. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  392. data/skills/rigor-plugin-review/SKILL.md +6 -4
  393. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  394. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  395. data/skills/rigor-project-init/SKILL.md +46 -18
  396. data/skills/rigor-project-init/references/01-detect.md +13 -9
  397. data/skills/rigor-project-init/references/02-configure.md +40 -10
  398. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  399. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  400. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  401. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  402. data/skills/rigor-type-oracle/SKILL.md +4 -6
  403. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  404. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  405. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  406. data/skills/rigor-upgrade/SKILL.md +13 -8
  407. metadata +132 -1
@@ -32,9 +32,19 @@ module Rigor
32
32
  # `docs/internal-spec/inference-engine.md` § "Declaration-sourced provenance mark (ADR-58)"; keep this
33
33
  # comment subordinate to it.
34
34
  #
35
- # So `marked?` matches exactly the two node shapes the scope actually models and invents no propagation
36
- # of its own; anything flow-live keeps firing.
35
+ # So `marked?` matches exactly the node shapes the scope actually models and invents no propagation of its
36
+ # own; anything flow-live keeps firing.
37
+ #
38
+ # Issue #1362 adds a third carrier, a global still on its program-global seed, which joins the non-nil part of
39
+ # the declared type of a global Ruby's own signatures declare with the file's writes (ADR-117 Decision point
40
+ # 2: never warn that a value might deviate from the idiom). Its declared members are the declaration-sourced
41
+ # part. The consumers judge such a value by the file's writes ({.written_type}, {.withholds_nil?}), and a
42
+ # value that mixes it with something else without the declared members the file never writes
43
+ # ({.declared_only_rejection?}), so a report the writes alone earn still fires.
37
44
  module DeclarationSourcedGuard
45
+ NO_GLOBALS = [].freeze
46
+ private_constant :NO_GLOBALS
47
+
38
48
  module_function
39
49
 
40
50
  # True when `node` is a direct read of a binding whose optionality is purely declaration-sourced. Any
@@ -44,9 +54,164 @@ module Rigor
44
54
  case node
45
55
  when Prism::InstanceVariableReadNode then scope.declaration_sourced?(:ivar, node.name)
46
56
  when Prism::LocalVariableReadNode then scope.declaration_sourced?(:local, node.name)
57
+ when Prism::GlobalVariableReadNode then scope.declaration_sourced?(:global, node.name)
47
58
  else false
48
59
  end
49
60
  end
61
+
62
+ # The globals whose declared seeds `node` still reads, empty for none: a read of a marked global, or of a
63
+ # local copied from one or more (`sep = $/`, `Scope#declaration_sourced_global_copies`), bare or in
64
+ # parentheses (`($stdout)`).
65
+ def global_sources(node, scope)
66
+ node = unparenthesised(node)
67
+ case node
68
+ when Prism::GlobalVariableReadNode
69
+ scope.declaration_sourced?(:global, node.name) ? [node.name] : NO_GLOBALS
70
+ when Prism::LocalVariableReadNode
71
+ scope.declaration_sourced_global_copies(node.name)
72
+ else NO_GLOBALS
73
+ end
74
+ end
75
+
76
+ # The globals a local written from `value` copies the declared seeds of, empty for none: a read of a marked
77
+ # global, bare or in parentheses (`sep = ($/)`), or a bare read of a local that copies some (`s = sep`).
78
+ # Unlike an ivar copy's, this mark follows bare local-to-local copies; a method result (`$/.dup`) or a
79
+ # container element (`[$/].each { |s| … }`) carries none, as ADR-58's one-hop boundary has it.
80
+ def copied_globals(value, scope)
81
+ return scope.declaration_sourced_global_copies(value.name) if value.is_a?(Prism::LocalVariableReadNode)
82
+
83
+ read = unparenthesised(value)
84
+ return NO_GLOBALS unless read.is_a?(Prism::GlobalVariableReadNode) &&
85
+ scope.declaration_sourced?(:global, read.name)
86
+
87
+ [read.name]
88
+ end
89
+
90
+ # `node` with its enclosing parentheses taken off while they hold a single expression.
91
+ def unparenthesised(node)
92
+ while node.is_a?(Prism::ParenthesesNode)
93
+ body = node.body
94
+ break unless body.is_a?(Prism::StatementsNode) && body.body.size == 1
95
+
96
+ node = body.body.first
97
+ end
98
+ node
99
+ end
100
+
101
+ # The union of the file's own writes to the globals `node` reads the seeds of ({.global_sources}), or nil.
102
+ def written_type(node, scope)
103
+ sources = global_sources(node, scope)
104
+ return nil if sources.empty?
105
+
106
+ written = sources.map { |source| scope.program_globals[source] }
107
+ written.include?(nil) ? nil : Type::Combinator.union(*written)
108
+ end
109
+
110
+ # True when a `nil` in `node`'s type is not diagnostic fuel: for a global's seed or its copy, when the file
111
+ # never writes `nil` to the global, so the declaration alone contributes it; otherwise when `node` carries
112
+ # the ADR-58 mark.
113
+ def withholds_nil?(node, scope)
114
+ written = written_type(node, scope)
115
+ return marked?(node, scope) if written.nil?
116
+
117
+ !nil_bearing?(written)
118
+ end
119
+
120
+ # Issue #1362 — true when every member of `type` that `expected` rejects is a declared member of a builtin
121
+ # global this file writes and joins ({#declared_only_members}), so the rejection rests on the join alone. The
122
+ # argument and return gates read it for a value that mixes such a global with something else
123
+ # (`c ? $stdout : StringIO.new`), which the ADR-58 mark does not follow. It is a type-level reading: it also
124
+ # withholds a rejection of such a member that comes from elsewhere in the file (`STDOUT` passed where a
125
+ # `StringIO` is required, in a file that writes `$stdout`).
126
+ #
127
+ # Otherwise the value with those members taken out of every union inside it ({#without_declared_only}) is
128
+ # handed to the block, which answers whether the caller's rule accepts it: the value as it was before the
129
+ # join, one level into a container it builds as well (`[$stdout]` or `{ io: $stdout }` passed where an
130
+ # `Array[StringIO]` is required), and with a `nil` the file writes judged as the rule judges a `nil`.
131
+ def declared_only_rejection?(type, expected, scope)
132
+ declared_only = declared_only_members(scope)
133
+ return false if declared_only.empty?
134
+
135
+ members = type.is_a?(Type::Union) ? type.members : [type]
136
+ rejected = members.select { |member| Inference::Acceptance.accepts(expected, member, mode: :gradual).no? }
137
+ return false if rejected.empty?
138
+ return true if rejected.all? { |member| declared_only.include?(member) }
139
+
140
+ stripped = without_declared_only(type, declared_only)
141
+ !stripped.equal?(type) && yield(stripped)
142
+ end
143
+
144
+ # `type` with each member of `declared_only` taken out of every union in it, through tuples, hash shapes and
145
+ # generic arguments; a union with nothing else left keeps its members. Answers `type` itself when nothing
146
+ # was taken out.
147
+ def without_declared_only(type, declared_only)
148
+ case type
149
+ when Type::Union then union_without_declared_only(type, declared_only)
150
+ when Type::Tuple
151
+ elements = type.elements.map { |element| without_declared_only(element, declared_only) }
152
+ elements == type.elements ? type : Type::Combinator.tuple_of(*elements)
153
+ when Type::HashShape
154
+ pairs = type.pairs.transform_values { |value| without_declared_only(value, declared_only) }
155
+ pairs == type.pairs ? type : rebuild_hash_shape(type, pairs)
156
+ when Type::Nominal
157
+ args = type.type_args.map { |arg| without_declared_only(arg, declared_only) }
158
+ args == type.type_args ? type : Type::Combinator.nominal_of(type.class_name, type_args: args)
159
+ else type
160
+ end
161
+ end
162
+
163
+ def union_without_declared_only(union, declared_only)
164
+ kept = union.members.reject { |member| declared_only.include?(member) }
165
+ kept = union.members if kept.empty?
166
+ rebuilt = kept.map { |member| without_declared_only(member, declared_only) }
167
+ rebuilt == union.members ? union : Type::Combinator.union(*rebuilt)
168
+ end
169
+
170
+ def rebuild_hash_shape(shape, pairs)
171
+ Type::Combinator.hash_shape_of(
172
+ pairs, required_keys: shape.required_keys, optional_keys: shape.optional_keys,
173
+ read_only_keys: shape.read_only_keys, extra_keys: shape.extra_keys
174
+ )
175
+ end
176
+
177
+ # For each builtin global this file writes and joins, the non-nil members of its declared type that the file's
178
+ # writes to that global do not hold (`IO` after `$stdout = StringIO.new`, nothing after `$stderr = STDERR`),
179
+ # gathered over the globals. A declared literal also counts in its class form, which a method called on the
180
+ # joined union returns: `$VERBOSE.itself` reads `FalseClass | TrueClass` after `$VERBOSE = true`.
181
+ def declared_only_members(scope)
182
+ seeds = scope.discovery.program_global_seeds
183
+ return NO_GLOBALS if seeds.empty?
184
+
185
+ seeds.keys.flat_map do |name|
186
+ written = type_members(scope.program_globals[name])
187
+ type_members(scope.environment.global_for_name(name, builtin: true))
188
+ .reject { |member| nil_bearing?(member) }
189
+ .flat_map { |member| [member, *class_form(member)] }
190
+ .reject { |member| written.include?(member) }
191
+ end.uniq
192
+ end
193
+
194
+ def class_form(member)
195
+ return NO_GLOBALS unless member.is_a?(Type::Constant)
196
+
197
+ [Type::Combinator.nominal_of(member.value.class.name)]
198
+ end
199
+
200
+ def type_members(type)
201
+ return NO_GLOBALS if type.nil?
202
+
203
+ type.is_a?(Type::Union) ? type.members : [type]
204
+ end
205
+
206
+ def nil_bearing?(type)
207
+ members = type.is_a?(Type::Union) ? type.members : [type]
208
+ members.any? do |member|
209
+ (member.is_a?(Type::Constant) && member.value.nil?) ||
210
+ (member.is_a?(Type::Nominal) && member.class_name == "NilClass")
211
+ end
212
+ end
213
+ private_class_method :nil_bearing?, :unparenthesised, :declared_only_members, :type_members, :class_form,
214
+ :without_declared_only, :union_without_declared_only, :rebuild_hash_shape
50
215
  end
51
216
  end
52
217
  end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../../inference/scope_indexer"
6
+ require_relative "../../source/constant_path"
7
+ require_relative "../../source/node_children"
8
+
9
+ module Rigor
10
+ module Analysis
11
+ module CheckRules
12
+ # Issue #1120 — the two ways one file makes a method callable at some sites and not at others, which
13
+ # `call.undefined-method` has to ask the file's syntax about because no project-wide table can answer:
14
+ #
15
+ # - **Refinements.** `using M` activates M's refinements from the end of the call to the end of the body
16
+ # that holds it — the file's top level or a `class` / `module` body, including everything nested in it —
17
+ # and nowhere else, not even in another file. A `refine X do … end` block is active inside itself. What
18
+ # each module refines is the project-wide `Scope#discovered_refinements`; this answers whether any of
19
+ # those modules is in effect at a call site. A `using` whose argument is not a constant
20
+ # (`using Module.new { refine … }`) names no module, so every refinement counts as in effect throughout
21
+ # its file. A `using` inside a `def` raises in Ruby and activates nothing.
22
+ # - **Singleton defs on locals.** `def o.m` defines `m` on the one object `o` holds. The local's type
23
+ # is not changed; `o.m` is simply not reported within the scope the `def` is written in (the
24
+ # enclosing `def`, `class` / `module` body, or file — a block shares its enclosing scope's locals).
25
+ #
26
+ # Built lazily: the walk runs the first time a would-fire call asks, so a file with no such call pays
27
+ # one small object and nothing else.
28
+ class LexicalMethodSites
29
+ def initialize(root)
30
+ @root = root
31
+ @built = false
32
+ end
33
+
34
+ # Is a refinement from one of `modules` (refining-module names) in effect at `call_node`?
35
+ def refinement_active?(call_node, modules)
36
+ build
37
+ return true if @unresolved_using
38
+
39
+ offset = call_node.location.start_offset
40
+ return true if @refine_blocks.any? { |start, stop| offset >= start && offset < stop }
41
+
42
+ @usings.any? do |start, stop, candidates|
43
+ offset >= start && offset < stop && candidates.any? { |name| modules.include?(name) }
44
+ end
45
+ end
46
+
47
+ # Issue #1367 — is any refinement in effect at `node`: a `using` of any module, a `using` of a non-constant, or
48
+ # the inside of a `refine` block? The `global.*` stream check asks it where it cannot name the refining
49
+ # module (a `define_method` or `alias_method` inside `refine` records none).
50
+ def using_in_effect?(node)
51
+ build
52
+ return true if @unresolved_using
53
+
54
+ offset = node.location.start_offset
55
+ @refine_blocks.any? { |start, stop| offset >= start && offset < stop } ||
56
+ @usings.any? { |start, stop, _candidates| offset >= start && offset < stop }
57
+ end
58
+
59
+ # Is `def_node` one of the defs a `refine X do … end` body defines on X? Such a def redefines X's method by
60
+ # design, so X's declared signature for the name is not its contract (issue #1120, maintainer ruling a′).
61
+ def refinement_def?(def_node)
62
+ build
63
+ @refinement_defs.include?(def_node.location.start_offset)
64
+ end
65
+
66
+ # Does a `def <local>.<name>` for this call's local receiver and method name sit in the scope the call
67
+ # is in?
68
+ def singleton_local_def?(call_node)
69
+ receiver = call_node.receiver
70
+ return false unless receiver.is_a?(Prism::LocalVariableReadNode)
71
+
72
+ build
73
+ offset = call_node.location.start_offset
74
+ @singleton_defs.any? do |local, method_name, start, stop|
75
+ local == receiver.name && method_name == call_node.name && offset >= start && offset < stop &&
76
+ !nested_scope_holds?(offset, start, stop)
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Is `offset` inside a local scope nested in the one `[start, stop)` spans? A `def` in the file's top level
83
+ # does not see the top level's `o`.
84
+ def nested_scope_holds?(offset, start, stop)
85
+ @scopes.any? do |inner_start, inner_stop|
86
+ (inner_start > start || inner_stop < stop) && inner_start >= start && inner_stop <= stop &&
87
+ offset >= inner_start && offset < inner_stop
88
+ end
89
+ end
90
+
91
+ def build
92
+ return if @built
93
+
94
+ @built = true
95
+ @usings = []
96
+ @refine_blocks = []
97
+ @singleton_defs = []
98
+ @scopes = []
99
+ @refinement_defs = Set.new
100
+ @unresolved_using = false
101
+ return if @root.nil?
102
+
103
+ location = @root.location
104
+ span = [location.start_offset, location.end_offset]
105
+ walk(@root, [], span, span, false)
106
+ end
107
+
108
+ # `body` is the `[start, end]` of the body a `using` here stays in effect to the end of; `locals` is the
109
+ # span of the scope a local written here belongs to.
110
+ def walk(node, prefix, body, locals, in_def)
111
+ case node
112
+ when Prism::ClassNode, Prism::ModuleNode
113
+ span = scope_span(node)
114
+ inner = Source::ConstantPath.declaration_prefix(prefix, node.constant_path) || prefix
115
+ return walk_children(node.body, inner, span, span, false)
116
+ when Prism::SingletonClassNode
117
+ walk(node.expression, prefix, body, locals, in_def)
118
+ span = scope_span(node)
119
+ return walk_children(node.body, prefix, span, span, false)
120
+ when Prism::DefNode
121
+ record_singleton_def(node, locals)
122
+ return walk_children(node.body, prefix, body, scope_span(node), true)
123
+ when Prism::CallNode
124
+ record_call(node, prefix, body, in_def)
125
+ end
126
+
127
+ walk_children(node, prefix, body, locals, in_def)
128
+ end
129
+
130
+ def walk_children(node, prefix, body, locals, in_def)
131
+ return if node.nil?
132
+
133
+ node.rigor_each_child { |child| walk(child, prefix, body, locals, in_def) }
134
+ end
135
+
136
+ def record_singleton_def(node, locals)
137
+ receiver = node.receiver
138
+ return unless receiver.is_a?(Prism::LocalVariableReadNode)
139
+
140
+ @singleton_defs << [receiver.name, node.name, *locals]
141
+ end
142
+
143
+ def record_call(node, prefix, body, in_def)
144
+ if Inference::ScopeIndexer.refine_target(node)
145
+ @refine_blocks << span_of(node.block)
146
+ record_refinement_defs(node.block.body)
147
+ elsif using_call?(node) && !in_def
148
+ record_using(node, prefix, body)
149
+ end
150
+ end
151
+
152
+ def record_refinement_defs(body)
153
+ return if body.nil?
154
+
155
+ Inference::ScopeIndexer.each_refinement_def(body) do |def_node|
156
+ @refinement_defs << def_node.location.start_offset
157
+ end
158
+ end
159
+
160
+ def using_call?(node)
161
+ node.name == :using && (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)) &&
162
+ node.arguments&.arguments&.size == 1
163
+ end
164
+
165
+ def record_using(node, prefix, body)
166
+ argument = node.arguments.arguments.first
167
+ if argument.is_a?(Prism::ConstantReadNode) || argument.is_a?(Prism::ConstantPathNode)
168
+ candidates = Inference::ScopeIndexer.constant_receiver_candidates(argument, prefix)
169
+ @usings << [node.location.end_offset, body.last, candidates]
170
+ else
171
+ @unresolved_using = true
172
+ end
173
+ end
174
+
175
+ # The span of a node that opens a local scope, recorded for {#nested_scope_holds?}.
176
+ def scope_span(node)
177
+ span = span_of(node)
178
+ @scopes << span
179
+ span
180
+ end
181
+
182
+ def span_of(node)
183
+ location = node.location
184
+ [location.start_offset, location.end_offset]
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -19,10 +19,12 @@ module Rigor
19
19
  # accumulated {Diagnostic}s, in the same emission order the inline `NodeWalker.each` produced them (the
20
20
  # shared walk is the same visit-before-descend DFS over `compact_child_nodes`).
21
21
  class MainPassCollector
22
- # The node classes the former inline pass branched on. A plain `Prism::IfNode` covers ternaries and
23
- # postfix `if` too (the legacy `when Prism::IfNode, Prism::UnlessNode` arm did the same).
22
+ # The node classes the main pass branches on. A plain `Prism::IfNode` covers ternaries and postfix `if`
23
+ # too (the legacy `when Prism::IfNode, Prism::UnlessNode` arm did the same). The three global-write
24
+ # classes carry issue #1367's `global.*` rules.
24
25
  NODE_CLASSES = [
25
- Prism::CallNode, Prism::DefNode, Prism::IfNode, Prism::UnlessNode
26
+ Prism::CallNode, Prism::DefNode, Prism::IfNode, Prism::UnlessNode,
27
+ Prism::GlobalVariableWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::MultiWriteNode
26
28
  ].freeze
27
29
 
28
30
  # @param node_diagnostics — maps a `Prism::Node` to the array of diagnostics the main pass emits
@@ -6,9 +6,9 @@ module Rigor
6
6
  # Canonical identifiers for each rule. Per ADR-8 § "Diagnostic ID family hierarchy", rule names are
7
7
  # `family.rule-name` two-segment strings; the families group diagnostics by where they originate
8
8
  # (`call.*` for call-site rules, `flow.*` for flow-analysis proofs, `assert.*` for runtime-assertion
9
- # rules, `dump.*` for debug helpers, `def.*` for method-definition rules). Used by the configuration
10
- # `disable:` list and the in-source `# rigor:disable <rule>` suppression comment system; new rules MUST
11
- # register here so user configuration can refer to them.
9
+ # rules, `dump.*` for debug helpers, `def.*` for method-definition rules, `global.*` for writes to special
10
+ # globals). Used by the configuration `disable:` list and the in-source `# rigor:disable <rule>`
11
+ # suppression comment system; new rules MUST register here so user configuration can refer to them.
12
12
  #
13
13
  # ADR-87 WD4 — this pure-data constant table is split out of the (engine-heavy) `check_rules.rb` so
14
14
  # {Analysis::RuleCatalog} — which the CLI's JSON `evidence_tier` / `documentation_url` enrichment reads
@@ -66,6 +66,18 @@ module Rigor
66
66
  # annotation must never turn collection on by itself (that would be a project-wide cost cliff
67
67
  # nobody asked for), and it must equally never be silently inert.
68
68
  RULE_EFFECT_ANNOTATIONS_UNCHECKED = "effect.annotations-unchecked"
69
+ # ADR-112 WD5 / #1075 — two statements of one member's contract that contradict: a `sig/` declaration
70
+ # against an inline `@rbs` / `#:` one (and, once #1073 reads it, `@rbs` / `#:` against `@extrbs`), or a
71
+ # `rigor:v1:` refinement outside the member's own declared type. `rbs.` because the finding is about
72
+ # the RBS the environment is built from, whichever file spelled it; not `rbs_extended.`, whose rows
73
+ # are payload validity, since most contradictions involve no `RBS::Extended` annotation at all.
74
+ RULE_CONTRADICTING_SIGNATURE = "rbs.contradicting-signature"
75
+ # Issue #1367 (ADR-117 WD2) — the `global.*` family: a write to a special global that its setter rejects, so
76
+ # the write raises on every run. Two ids so a project can tune the type check without hiding read-only
77
+ # writes; the `write` wording follows `def.ivar-write-mismatch`. The envelope is the interpreter's setter
78
+ # (`SpecialGlobalSetters`), not the global's RBS declaration.
79
+ RULE_GLOBAL_WRITE_TYPE_MISMATCH = "global.write-type-mismatch"
80
+ RULE_GLOBAL_READONLY_WRITE = "global.readonly-write"
69
81
 
70
82
  ALL_RULES = [
71
83
  RULE_UNDEFINED_METHOD,
@@ -91,6 +103,8 @@ module Rigor
91
103
  RULE_OVERRIDE_RETURN_WIDENED,
92
104
  RULE_OVERRIDE_PARAM_NARROWED,
93
105
  RULE_IVAR_WRITE_MISMATCH,
106
+ RULE_GLOBAL_WRITE_TYPE_MISMATCH,
107
+ RULE_GLOBAL_READONLY_WRITE,
94
108
  RULE_SUPPRESSION_UNKNOWN_RULE,
95
109
  RULE_SUPPRESSION_EMPTY,
96
110
  RULE_SUPPRESSION_UNKNOWN_MARKER,
@@ -98,7 +112,8 @@ module Rigor
98
112
  RULE_EFFECT_ENVELOPE_EXCEEDED,
99
113
  RULE_EFFECT_LISKOV_WIDENED,
100
114
  RULE_EFFECT_UNKNOWN_LABEL,
101
- RULE_EFFECT_ANNOTATIONS_UNCHECKED
115
+ RULE_EFFECT_ANNOTATIONS_UNCHECKED,
116
+ RULE_CONTRADICTING_SIGNATURE
102
117
  ].freeze
103
118
 
104
119
  # Backward-compat alias table (ADR-8 § "Backward compatibility"). Existing user code with
@@ -128,7 +143,7 @@ module Rigor
128
143
 
129
144
  # Family wildcard — a `<family>` token in a suppression comment or `disable:` list disables every rule
130
145
  # whose canonical id starts with `<family>.`. Per ADR-8 § "1".
131
- RULE_FAMILIES = %w[call flow assert dump def suppression static effect].freeze
146
+ RULE_FAMILIES = %w[call flow assert dump def global suppression static effect].freeze
132
147
 
133
148
  # Families of diagnostics the engine emits OUTSIDE the CheckRules catalogue (aggregator-level and
134
149
  # reporter-level diagnostics such as `rbs_extended.unsatisfied-conformance`,
@@ -50,6 +50,7 @@ module Rigor
50
50
  Struct: %i[new],
51
51
  Data: %i[define]
52
52
  }.freeze
53
+ Ractor.make_shareable(DETACHED_CLASS_FACTORIES)
53
54
  private_constant :DETACHED_CLASS_FACTORIES
54
55
 
55
56
  EMPTY_METHODS = [].freeze
@@ -108,7 +109,7 @@ module Rigor
108
109
  end
109
110
  end
110
111
 
111
- # The context the children of `node` descend under. Mirrors {RuleWalk.descend}.
112
+ # Mirrors {RuleWalk.descend}.
112
113
  def descend(node, context)
113
114
  RuleWalk.descend(node, context)
114
115
  end