rigortype 0.3.9 → 0.4.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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  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/10-sorbet.md +9 -10
  14. data/docs/handbook/11-sig-gen.md +454 -13
  15. data/docs/manual/02-cli-reference.md +63 -2
  16. data/docs/manual/03-configuration.md +7 -0
  17. data/docs/manual/04-diagnostics.md +4 -0
  18. data/docs/manual/07-plugins.md +1 -1
  19. data/docs/manual/10-mcp-server.md +3 -2
  20. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  21. data/docs/manual/19-effect-labels.md +10 -0
  22. data/docs/manual/plugins/README.md +7 -0
  23. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  24. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  25. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  26. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  27. data/docs/manual/plugins/rigor-activejob.md +7 -0
  28. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  29. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  30. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  31. data/docs/manual/plugins/rigor-grape.md +106 -0
  32. data/docs/manual/plugins/rigor-graphql.md +23 -2
  33. data/docs/manual/plugins/rigor-pundit.md +8 -1
  34. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  35. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  36. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  37. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  38. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  39. data/exe/rigor +19 -4
  40. data/lib/rigor/analysis/baseline.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  42. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  43. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  49. data/lib/rigor/analysis/check_rules.rb +520 -49
  50. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  51. data/lib/rigor/analysis/fact_store.rb +9 -0
  52. data/lib/rigor/analysis/incremental.rb +26 -0
  53. data/lib/rigor/analysis/incremental_session.rb +36 -4
  54. data/lib/rigor/analysis/project_scan.rb +11 -1
  55. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  56. data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
  57. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  58. data/lib/rigor/analysis/run_cache_key.rb +20 -11
  59. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
  60. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  61. data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
  62. data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
  63. data/lib/rigor/analysis/runner.rb +278 -20
  64. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  65. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  66. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  67. data/lib/rigor/analysis/template_units.rb +399 -0
  68. data/lib/rigor/analysis/worker_session.rb +53 -10
  69. data/lib/rigor/bleeding_edge.rb +0 -2
  70. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  71. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  72. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  73. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  74. data/lib/rigor/cache/descriptor.rb +6 -1
  75. data/lib/rigor/cache/engine_source.rb +29 -1
  76. data/lib/rigor/cache/incremental_snapshot.rb +33 -1
  77. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  78. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  79. data/lib/rigor/cache/store.rb +0 -1
  80. data/lib/rigor/ci_detector.rb +1 -0
  81. data/lib/rigor/cli/doc_links.rb +1 -1
  82. data/lib/rigor/cli/docs_command.rb +4 -4
  83. data/lib/rigor/cli/plugin_command.rb +3 -3
  84. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  85. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  86. data/lib/rigor/cli/skill_command.rb +1 -1
  87. data/lib/rigor/cli/skill_describe.rb +0 -1
  88. data/lib/rigor/cli/type_of_command.rb +19 -5
  89. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  90. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  91. data/lib/rigor/cli.rb +21 -3
  92. data/lib/rigor/configuration/severity_profile.rb +19 -3
  93. data/lib/rigor/configuration.rb +66 -3
  94. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  95. data/lib/rigor/effects/attribution.rb +11 -2
  96. data/lib/rigor/effects/callee_rule.rb +368 -0
  97. data/lib/rigor/effects/catalog.rb +7 -4
  98. data/lib/rigor/effects/collector.rb +11 -5
  99. data/lib/rigor/effects/config_envelopes.rb +9 -2
  100. data/lib/rigor/effects/definition_context.rb +179 -0
  101. data/lib/rigor/effects/effect_table.rb +11 -3
  102. data/lib/rigor/effects/envelope_check.rb +1 -1
  103. data/lib/rigor/effects/envelope_index.rb +15 -0
  104. data/lib/rigor/effects/file_collection.rb +59 -5
  105. data/lib/rigor/effects/framework_units.rb +1 -1
  106. data/lib/rigor/effects/identity.rb +16 -0
  107. data/lib/rigor/effects/local_ownership.rb +38 -12
  108. data/lib/rigor/effects/method_key.rb +21 -0
  109. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  110. data/lib/rigor/effects/plugin_facts.rb +43 -31
  111. data/lib/rigor/effects/propagator.rb +295 -16
  112. data/lib/rigor/effects/registry.rb +1 -1
  113. data/lib/rigor/effects/scanner.rb +121 -72
  114. data/lib/rigor/effects/signature_sources.rb +1 -1
  115. data/lib/rigor/effects/snapshot.rb +2 -1
  116. data/lib/rigor/effects/summary.rb +27 -4
  117. data/lib/rigor/effects/unit_scan.rb +385 -30
  118. data/lib/rigor/effects/visibility.rb +101 -0
  119. data/lib/rigor/environment/lockfile_resolver.rb +17 -0
  120. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  121. data/lib/rigor/environment/member_consistency.rb +298 -0
  122. data/lib/rigor/environment/rbs_loader.rb +399 -133
  123. data/lib/rigor/environment.rb +103 -38
  124. data/lib/rigor/hashing/xxh3.rb +264 -0
  125. data/lib/rigor/inference/acceptance.rb +139 -9
  126. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  127. data/lib/rigor/inference/block_call_timing.rb +338 -0
  128. data/lib/rigor/inference/block_parameter_binder.rb +73 -27
  129. data/lib/rigor/inference/block_repetition.rb +71 -0
  130. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  131. data/lib/rigor/inference/budget_trace.rb +2 -1
  132. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  133. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  134. data/lib/rigor/inference/captured_locals.rb +387 -15
  135. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  136. data/lib/rigor/inference/content_join.rb +200 -27
  137. data/lib/rigor/inference/def_return_typer.rb +11 -7
  138. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  139. data/lib/rigor/inference/element_read_widening.rb +22 -10
  140. data/lib/rigor/inference/error_info.rb +196 -0
  141. data/lib/rigor/inference/expression_typer.rb +1532 -496
  142. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  143. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  144. data/lib/rigor/inference/global_write_census.rb +239 -0
  145. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  146. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  147. data/lib/rigor/inference/index_write_widening.rb +16 -3
  148. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  149. data/lib/rigor/inference/jump_targets.rb +82 -0
  150. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  151. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  152. data/lib/rigor/inference/last_line.rb +340 -0
  153. data/lib/rigor/inference/last_status.rb +144 -0
  154. data/lib/rigor/inference/macro_block_self_type.rb +167 -17
  155. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  156. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  157. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  158. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  159. data/lib/rigor/inference/match_rebinding.rb +392 -0
  160. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  161. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  162. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  163. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  164. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  165. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  166. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  167. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  168. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  169. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
  170. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  171. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  172. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  173. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  174. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  175. data/lib/rigor/inference/multi_target_binder.rb +340 -52
  176. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  177. data/lib/rigor/inference/mutation_widening.rb +70 -48
  178. data/lib/rigor/inference/narrowing.rb +540 -120
  179. data/lib/rigor/inference/operand_effects.rb +167 -0
  180. data/lib/rigor/inference/operand_walk.rb +88 -0
  181. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  182. data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
  183. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  184. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  185. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  186. data/lib/rigor/inference/receiver_alias.rb +90 -1
  187. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  188. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  189. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  190. data/lib/rigor/inference/return_barrier.rb +54 -0
  191. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  192. data/lib/rigor/inference/scope_indexer.rb +4615 -551
  193. data/lib/rigor/inference/statement_evaluator.rb +3176 -490
  194. data/lib/rigor/inference/stored_block_call.rb +54 -0
  195. data/lib/rigor/inference/string_mutation.rb +44 -7
  196. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  197. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  198. data/lib/rigor/language_server/debouncer.rb +0 -1
  199. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  200. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  201. data/lib/rigor/language_server/project_context.rb +5 -3
  202. data/lib/rigor/mcp/server.rb +2 -1
  203. data/lib/rigor/plugin/base.rb +168 -5
  204. data/lib/rigor/plugin/box_probe.rb +91 -0
  205. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  206. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  207. data/lib/rigor/plugin/loader.rb +2 -1
  208. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  209. data/lib/rigor/plugin/manifest.rb +71 -10
  210. data/lib/rigor/plugin/registry.rb +35 -1
  211. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  212. data/lib/rigor/plugin/template_unit.rb +196 -0
  213. data/lib/rigor/plugin.rb +1 -0
  214. data/lib/rigor/protection/discovery_seed.rb +3 -1
  215. data/lib/rigor/protection/kill_signature.rb +0 -1
  216. data/lib/rigor/protection/mutation_cache.rb +1 -2
  217. data/lib/rigor/rbs_extended.rb +27 -0
  218. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  219. data/lib/rigor/reflection/constant_path.rb +19 -6
  220. data/lib/rigor/reflection.rb +72 -105
  221. data/lib/rigor/scope/discovery_index.rb +135 -2
  222. data/lib/rigor/scope.rb +859 -49
  223. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  224. data/lib/rigor/sig_gen/classification.rb +22 -5
  225. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  226. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  227. data/lib/rigor/sig_gen/generator.rb +558 -31
  228. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  229. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  230. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  231. data/lib/rigor/sig_gen/renderer.rb +124 -9
  232. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  233. data/lib/rigor/sig_gen/write_result.rb +19 -3
  234. data/lib/rigor/sig_gen/writer.rb +166 -35
  235. data/lib/rigor/sig_gen.rb +3 -0
  236. data/lib/rigor/signature_path_audit.rb +1 -1
  237. data/lib/rigor/source/node_walker.rb +0 -3
  238. data/lib/rigor/source/parameter_envelope.rb +72 -0
  239. data/lib/rigor/source.rb +1 -0
  240. data/lib/rigor/type/combinator.rb +88 -12
  241. data/lib/rigor/type/difference.rb +1 -0
  242. data/lib/rigor/type/hash_shape.rb +1 -1
  243. data/lib/rigor/type/refined.rb +1 -0
  244. data/lib/rigor/version.rb +1 -1
  245. data/lib/rigor.rb +1 -0
  246. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  247. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  248. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  249. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  250. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  251. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  252. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  253. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  254. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  255. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  256. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  257. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  258. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  259. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  260. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  261. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  262. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  263. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  264. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  265. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  266. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  267. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  268. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  269. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  270. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  271. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  272. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  273. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  274. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  275. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  276. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  277. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  278. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  279. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  280. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  281. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  282. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  283. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  284. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  285. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  286. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  287. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  288. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  289. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  290. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  291. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  292. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  293. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  294. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  295. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  296. data/sig/rigor/analysis/baseline.rbs +69 -7
  297. data/sig/rigor/analysis/fact_store.rbs +1 -1
  298. data/sig/rigor/analysis/project_scan.rbs +74 -0
  299. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  300. data/sig/rigor/effects/effect_table.rbs +60 -0
  301. data/sig/rigor/effects/envelope.rbs +97 -0
  302. data/sig/rigor/effects/envelope_index.rbs +33 -0
  303. data/sig/rigor/effects/file_collection.rbs +81 -0
  304. data/sig/rigor/effects/label.rbs +26 -0
  305. data/sig/rigor/effects/label_set.rbs +46 -0
  306. data/sig/rigor/effects/method_key.rbs +24 -0
  307. data/sig/rigor/effects/origin.rbs +50 -0
  308. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  309. data/sig/rigor/effects/registry.rbs +68 -0
  310. data/sig/rigor/effects/summary.rbs +50 -0
  311. data/sig/rigor/effects/taint_cause.rbs +12 -0
  312. data/sig/rigor/environment.rbs +12 -10
  313. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  314. data/sig/rigor/inference.rbs +6 -4
  315. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  316. data/sig/rigor/plugin/base.rbs +30 -7
  317. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  318. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  319. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  320. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  321. data/sig/rigor/plugin/io_boundary.rbs +1 -1
  322. data/sig/rigor/plugin/loader.rbs +3 -3
  323. data/sig/rigor/plugin/manifest.rbs +50 -11
  324. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  325. data/sig/rigor/plugin/registry.rbs +62 -1
  326. data/sig/rigor/plugin.rbs +1 -1
  327. data/sig/rigor/rbs_extended.rbs +1 -1
  328. data/sig/rigor/reflection.rbs +7 -6
  329. data/sig/rigor/scope.rbs +135 -20
  330. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  331. data/sig/rigor/source.rbs +4 -4
  332. data/sig/rigor/testing.rbs +10 -4
  333. data/sig/rigor/type.rbs +6 -0
  334. data/sig/rigor.rbs +38 -20
  335. data/skills/rigor-ask/SKILL.md +8 -5
  336. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  337. data/skills/rigor-ci-setup/SKILL.md +17 -21
  338. data/skills/rigor-doctor/SKILL.md +24 -22
  339. data/skills/rigor-doctor/references/01-checks.md +97 -33
  340. data/skills/rigor-editor-setup/SKILL.md +6 -4
  341. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  342. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  343. data/skills/rigor-next-steps/SKILL.md +4 -2
  344. data/skills/rigor-plugin-author/SKILL.md +19 -23
  345. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  346. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  347. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  348. data/skills/rigor-plugin-review/SKILL.md +6 -4
  349. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  350. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  351. data/skills/rigor-project-init/SKILL.md +9 -7
  352. data/skills/rigor-project-init/references/01-detect.md +13 -9
  353. data/skills/rigor-project-init/references/02-configure.md +33 -8
  354. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  355. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  356. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  357. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  358. data/skills/rigor-type-oracle/SKILL.md +4 -6
  359. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  360. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  361. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  362. data/skills/rigor-upgrade/SKILL.md +13 -8
  363. metadata +108 -1
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ class Environment
5
+ # ADR-112 WD5 / issue #1075 — the consistency rule for one member declared by two sources. It replaces
6
+ # ADR-32 WD13's "`sig/` wins per member": a `sig/` declaration and an inline (`@rbs` / `#:`) one of the
7
+ # same member are compared instead of ranked.
8
+ #
9
+ # - **Consistent** — in every type position one side's type is a subtype of the other's, decided from
10
+ # the two declarations alone ({Comparator#subset?}). `untyped` (and
11
+ # `void` / `top`, which RBS defines as the same top type) is consistent with everything and is the
12
+ # least precise answer, so a migrating project's `sig/ -> untyped` beside an inline `-> void` stays
13
+ # quiet (ADR-93's herb case). The two merge to the more precise side: when every position of the
14
+ # inline member is at least as precise as `sig/`'s and one is strictly more, the inline member binds
15
+ # and the `sig/` one stands down; otherwise `sig/` binds. Nothing is reported either way. The merge
16
+ # takes the narrower type in parameter positions too, which is the author's stated contract.
17
+ # - **Contradiction** — proven: some position every call fills whose two types are disjoint by the RBS
18
+ # class hierarchy ({RbsProof}), positional arity ranges that cannot meet, or a keyword one side
19
+ # requires and the other cannot take in any form.
20
+ # `sig/` still binds, so the class builds, and the run reports `rbs.contradicting-signature` as an
21
+ # error.
22
+ # - **Undecided** — everything else: a position Rigor cannot read faithfully, two types it cannot prove
23
+ # disjoint, shapes that differ without contradicting, overloads that do not pair one to one, or each
24
+ # side more precise in a different position. `sig/` binds and the dropped inline member is reported
25
+ # at `:info` under ADR-32 WD12, as every inline member was before.
26
+ #
27
+ # The false-positive discipline holds the DEFINITION of a contradiction (ADR-112 WD5): every doubt
28
+ # lands in "undecided". {Comparator} carries the type work and says what counts as a proof.
29
+ #
30
+ # The same comparison answers `rbs-extended.md`'s "an annotation whose refinement exceeds the ordinary
31
+ # RBS contract is a conflict": a member-level `rigor:v1:return:` / `rigor:v1:param:` payload disjoint
32
+ # from the member's own declared type is reported under the same identifier.
33
+ #
34
+ # The comparison works on one member PAIR and knows nothing about where either side came from, so a
35
+ # third source (ADR-112's `@extrbs`, #1073) plugs in as another caller of {Comparator#compare}.
36
+ #
37
+ # Pure over RBS AST members: no environment is consulted, so {RbsLoader} derives the same answer from its
38
+ # inputs at build time and on an environment-cache HIT.
39
+ module MemberConsistency
40
+ # One outcome per `(class, method, kind)` key two sources declare, or per member whose refinement
41
+ # exceeds its own declared type (`outcome: :refinement`). `signature_path` / `signature_line` locate
42
+ # the `.rbs` member (nil for a refinement record read off an inline member); `virtual_name` is the
43
+ # synthesized buffer name of the inline source (nil for a refinement record read off a `.rbs` member).
44
+ Record = Data.define(:class_name, :method_name, :kind, :outcome, :detail, :signature_path,
45
+ :signature_line, :virtual_name)
46
+
47
+ # What the environment build needs from the rule: which inline keys stand down, and which `.rbs`
48
+ # members stand down per file (because the inline side was the more precise one).
49
+ Resolution = Data.define(:records, :inline_standdowns, :signature_standdowns)
50
+
51
+ EMPTY = Ractor.make_shareable(
52
+ Resolution.new(records: [], inline_standdowns: Set.new, signature_standdowns: {})
53
+ )
54
+
55
+ # Outcomes that report, and how loud.
56
+ ERROR_OUTCOMES = %i[contradiction refinement].freeze
57
+ INFO_OUTCOMES = %i[undecided].freeze
58
+
59
+ NO_RECORDS = [].freeze
60
+ private_constant :NO_RECORDS
61
+
62
+ # What a disjointness proof may lean on (ADR-112 WD5, #1075): the RBS class hierarchy the analysis
63
+ # itself uses, restricted to classes Ruby core and the stdlib declare. Never Ruby constants loaded in
64
+ # the analyzer process — those depend on which libraries happen to be required, and rbs and Ruby
65
+ # disagree about some of them (`Tempfile < File` in rbs, `< Delegator` in Ruby). Never a class the
66
+ # project declares, whether in `sig/`, inline or only in Ruby: a project `sig/` routinely omits a
67
+ # superclass (`class Admin` for `Admin < User`), and a `Struct.new` / `Data.define` / `Class.new`
68
+ # constant has none in RBS at all, so the rest of Rigor never reads two of them as disjoint either.
69
+ #
70
+ # A core or stdlib class may be reopened by the project (a `sig/` monkeypatch); the proof still
71
+ # counts it, since a reopening can add modules but never change the superclass chain a class-versus-
72
+ # class answer reads. Nothing here depends on which files the run was given, so `rigor check lib` and
73
+ # `rigor check lib/one.rb` report the same rows.
74
+ #
75
+ # Only the reported rows are derived with a proof; the environment build is not, and needs none:
76
+ # a contradiction and an undecided pair both keep the `.rbs` member, so a proof changes a row's
77
+ # severity and never which declaration binds.
78
+ class RbsProof
79
+ # Core and stdlib classes whose RBS ancestry disagrees with Ruby's, excluded from every proof on
80
+ # both sides of each disagreement. rbs 4.2 declares `class Tempfile < File` where the `tempfile`
81
+ # library defines `Tempfile < Delegator` (so RBS reads `Tempfile` and `Delegator` as unrelated),
82
+ # `class Random` with no superclass where Ruby has `Random < Random::Base`, and
83
+ # `class Gem::MissingSpecError` with no superclass where RubyGems has it `< Gem::LoadError`. A pair
84
+ # naming one of these is undecided. `spec/rigor/environment/member_consistency_ancestry_guard_spec.rb` compares
85
+ # every candidate class's RBS chain with Ruby's and fails on a disagreement missing from this list,
86
+ # so an rbs or Ruby bump that introduces one fails CI instead of reporting an error on correct code.
87
+ UNRELIABLE_ANCESTRY = %w[Tempfile Delegator Random Random::Base Gem::MissingSpecError].to_set.freeze
88
+
89
+ # @param loader — the project's {RbsLoader}, whose built environment answers the hierarchy.
90
+ def initialize(loader)
91
+ @loader = loader
92
+ @ancestors = {}
93
+ end
94
+
95
+ # The RBS ancestor names of `name` (itself first) when its primary RBS declaration is a class from
96
+ # Ruby core or the stdlib, or nil.
97
+ def class_ancestors(name)
98
+ key = name.to_s.delete_prefix("::")
99
+ return @ancestors[key] if @ancestors.key?(key)
100
+
101
+ @ancestors[key] = core_class_ancestors(key)
102
+ end
103
+
104
+ private
105
+
106
+ def core_class_ancestors(key)
107
+ return nil if UNRELIABLE_ANCESTRY.include?(key)
108
+ return nil unless @loader.core_or_stdlib_class?(key) && !@loader.rbs_module?(key)
109
+
110
+ ancestors = @loader.ancestor_names_for(key)
111
+ ancestors.first == key ? ancestors : nil
112
+ end
113
+ end
114
+
115
+ class << self
116
+ # @param signature_members — `{[class, method, kind] => [signature_path, member, visibility]}`, the
117
+ # first project `.rbs` member declaring each key, with its effective visibility.
118
+ # @param inline_members — `[[virtual_name, class_name, member, visibility], ...]`, every member the
119
+ # inline sources declare.
120
+ # @param shadowable — the first segment of every class / module name the project's inputs declare
121
+ # ({Comparator#initialize}).
122
+ # @param proof — an {RbsProof} for the reported rows, or nil for the environment build.
123
+ # @return a {Resolution}. Records are sorted and deduplicated; silent outcomes are recorded too, so a
124
+ # caller can see what merged where.
125
+ def resolve(signature_members, inline_members, shadowable: Set.new, proof: nil)
126
+ return EMPTY if inline_members.empty?
127
+
128
+ # Loaded here rather than with the file: {RbsLoader} requires this module on every run, and only a
129
+ # project with inline RBS ever compares anything.
130
+ require_relative "member_consistency/comparator"
131
+
132
+ state = { comparator: Comparator.new(shadowable, proof: proof), records: [], inline: Set.new,
133
+ signature: Hash.new { |hash, file| hash[file] = Set.new }, checked: {}.compare_by_identity }
134
+ inline_members.each do |virtual_name, class_name, member, visibility|
135
+ state[:records].concat(refinement_records(state, class_name, member, nil, virtual_name))
136
+ resolve_member(state, signature_members, [virtual_name, class_name, member, visibility])
137
+ end
138
+ Resolution.new(
139
+ records: state[:records].uniq.sort_by { |r| record_sort_key(r) }.freeze,
140
+ inline_standdowns: state[:inline].freeze,
141
+ signature_standdowns: state[:signature].transform_values(&:freeze).freeze
142
+ )
143
+ end
144
+
145
+ # The `[method_name, kind]` pairs one RBS member contributes to a definition build, mirroring
146
+ # `RBS::DefinitionBuilder::MethodBuilder#build_instance` / `#build_singleton` — which is what decides
147
+ # whether two members collide. An attribute contributes its reader and / or writer name (`foo`,
148
+ # `foo=`), an alias its new name, and `def self?.x` both sides. An `overloading?` member (`def x: ...
149
+ # | ...`) contributes NOTHING: rbs files those under `overloads` rather than `originals`, so they are
150
+ # designed to compose with an existing declaration and are never compared against it.
151
+ def member_method_keys(member)
152
+ case member
153
+ when ::RBS::AST::Members::MethodDefinition then method_definition_keys(member)
154
+ when ::RBS::AST::Members::AttrReader then [[member.name, member.kind]]
155
+ when ::RBS::AST::Members::AttrWriter then [[:"#{member.name}=", member.kind]]
156
+ when ::RBS::AST::Members::AttrAccessor then [[member.name, member.kind], [:"#{member.name}=", member.kind]]
157
+ when ::RBS::AST::Members::Alias then [[member.new_name, member.kind]]
158
+ else []
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def resolve_member(state, signature_members, inline)
165
+ _virtual_name, class_name, member, = inline
166
+ overlapping = member_method_keys(member).filter_map do |method_name, kind|
167
+ key = [class_name, method_name, kind]
168
+ owner = signature_members[key]
169
+ owner && [key, *owner]
170
+ end
171
+ return if overlapping.empty?
172
+
173
+ verdicts = overlapping.map do |key, path, sig_member, sig_visibility|
174
+ record_signature_refinements(state, class_name, sig_member, path)
175
+ [key, path, sig_member, state[:comparator].compare(sig_member, member, key[1]), sig_visibility]
176
+ end
177
+ binds = inline_binds?(state[:comparator], verdicts, overlapping.map(&:first), inline)
178
+ apply_verdicts(state, verdicts, binds, inline[0])
179
+ end
180
+
181
+ # Records each key's outcome and files the losing side: every `.rbs` member the inline one displaces
182
+ # when it binds, or every shared inline key when `sig/` does.
183
+ def apply_verdicts(state, verdicts, inline_binds, virtual_name)
184
+ verdicts.each do |key, path, sig_member, verdict, _|
185
+ if inline_binds
186
+ state[:signature][path].merge(full_keys(key[0], sig_member))
187
+ verdict = [:inline, nil]
188
+ else
189
+ state[:inline] << key
190
+ verdict = reported_verdict(verdict)
191
+ end
192
+ state[:records] << record(key, verdict, path, sig_member, virtual_name)
193
+ end
194
+ end
195
+
196
+ # The inline member binds only when every key it shares is at least as precise inline, one is
197
+ # strictly more, and swapping loses nothing the `.rbs` member said:
198
+ #
199
+ # - each `.rbs` member it displaces declares no key the inline member does not also declare (an
200
+ # `attr_accessor` in `sig/` against an inline `def foo` would otherwise take `foo=` down with it);
201
+ # - the two have the same effective visibility (a `private def` in `sig/` must not turn public);
202
+ # - every annotation on the `.rbs` member other than a `rigor:v1:return:` / `rigor:v1:param:`
203
+ # refinement (which the comparison has already weighed) is also on the inline member, so a
204
+ # predicate, an assertion or an effect envelope written in `sig/` does not vanish (ADR-32 WD12).
205
+ def inline_binds?(comparator, verdicts, shared_keys, inline)
206
+ outcomes = verdicts.map { |verdict| verdict[3].first }
207
+ return false unless outcomes.all? { |outcome| %i[equal inline].include?(outcome) }
208
+ return false unless outcomes.include?(:inline)
209
+
210
+ shared = shared_keys.to_set
211
+ verdicts.all? do |_, _, sig_member, _, sig_visibility|
212
+ full_keys(inline[1], sig_member).all? { |key| shared.include?(key) } &&
213
+ sig_visibility == inline[3] &&
214
+ annotations_kept?(comparator, sig_member, inline[2])
215
+ end
216
+ end
217
+
218
+ def annotations_kept?(comparator, sig_member, inline_member)
219
+ kept = comparator.member_annotation_strings(inline_member)
220
+ comparator.member_annotation_strings(sig_member).all? do |string|
221
+ comparator.refinement_directive?(string) || kept.include?(string)
222
+ end
223
+ end
224
+
225
+ # A key where the inline side was more precise, but the member as a whole could not bind, lost
226
+ # something the author wrote: that is WD12's "parsed and not honoured", so it reports as undecided.
227
+ def reported_verdict(verdict)
228
+ if verdict.first == :inline
229
+ return [:undecided,
230
+ "the inline declaration is more precise here but could not replace the `.rbs` member " \
231
+ "without dropping what it declares"]
232
+ end
233
+
234
+ verdict
235
+ end
236
+
237
+ def full_keys(class_name, member)
238
+ member_method_keys(member).map { |method_name, kind| [class_name, method_name, kind] }
239
+ end
240
+
241
+ # The `.rbs` line is read only for an outcome that reports it: asking an `RBS::Buffer` for a line
242
+ # builds that buffer's whole line table, a cost a silent merge has no use for.
243
+ def record(key, verdict, path, sig_member, virtual_name)
244
+ line = ERROR_OUTCOMES.include?(verdict[0]) ? member_line(sig_member) : nil
245
+ Record.new(
246
+ class_name: key[0], method_name: key[1], kind: key[2], outcome: verdict[0], detail: verdict[1],
247
+ signature_path: path, signature_line: line, virtual_name: virtual_name
248
+ )
249
+ end
250
+
251
+ def record_signature_refinements(state, class_name, sig_member, path)
252
+ return if state[:checked].key?(sig_member)
253
+
254
+ state[:checked][sig_member] = true
255
+ state[:records].concat(refinement_records(state, class_name, sig_member, path, nil))
256
+ end
257
+
258
+ # `path` is the `.rbs` file for a `sig/` member (whose line is then read) and nil for an inline one.
259
+ def refinement_records(state, class_name, member, path, virtual_name)
260
+ conflicts = state[:comparator].refinement_conflicts(member)
261
+ return NO_RECORDS if conflicts.empty?
262
+
263
+ method_name, kind = member_method_keys(member).first || [member_name(member), :instance]
264
+ line = path && member_line(member)
265
+ conflicts.map do |detail|
266
+ Record.new(class_name: class_name, method_name: method_name, kind: kind, outcome: :refinement,
267
+ detail: detail, signature_path: path, signature_line: line, virtual_name: virtual_name)
268
+ end
269
+ end
270
+
271
+ def member_name(member)
272
+ member.respond_to?(:name) ? member.name : member.new_name
273
+ end
274
+
275
+ def member_line(member)
276
+ location = member.respond_to?(:location) ? member.location : nil
277
+ location.respond_to?(:start_line) ? location.start_line : nil
278
+ end
279
+
280
+ # `Symbol#name` and `nil.to_s` are frozen, so the key allocates nothing but itself.
281
+ def record_sort_key(record)
282
+ [record.class_name, record.method_name.name, record.kind.name, record.outcome.name,
283
+ record.virtual_name.to_s, record.signature_path.to_s, record.detail.to_s]
284
+ end
285
+
286
+ def method_definition_keys(member)
287
+ return [] if member.respond_to?(:overloading?) && member.overloading?
288
+
289
+ case member.kind
290
+ when :instance then [[member.name, :instance]]
291
+ when :singleton then [[member.name, :singleton]]
292
+ else [[member.name, :instance], [member.name, :singleton]] # `def self?.x` defines both sides
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end