rigortype 0.3.8 → 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 (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -3,6 +3,7 @@
3
3
  require "prism"
4
4
 
5
5
  require_relative "method_key"
6
+ require_relative "../source/constant_path"
6
7
 
7
8
  module Rigor
8
9
  module Effects
@@ -30,9 +31,9 @@ module Rigor
30
31
  NO_LINES = {}.freeze
31
32
  private_constant :NO_LINES
32
33
 
33
- # @param key [String] an effect unit key — `Tracer::Loud#emit`, `Net::HTTP.get`.
34
- # @param path [String] the file the key was traced to.
35
- # @return [Integer, nil] the `def`'s line, or nil when this file does not spell that key with a
34
+ # @param key — an effect unit key — `Tracer::Loud#emit`, `Net::HTTP.get`.
35
+ # @param path — the file the key was traced to.
36
+ # @return the `def`'s line, or nil when this file does not spell that key with a
36
37
  # `def` — an unreadable file, a syntax error, and a synthesized method all land here.
37
38
  def for(key:, path:)
38
39
  index_for(path)[key]
@@ -62,10 +63,10 @@ module Rigor
62
63
  def walk(node, nesting, singleton:, index:)
63
64
  case node
64
65
  when Prism::ModuleNode, Prism::ClassNode
65
- name = constant_name(node.constant_path)
66
- return if name.nil?
66
+ inner = header_nesting(nesting, node.constant_path)
67
+ return if inner.nil?
67
68
 
68
- walk_children(node.body, nesting + [name], singleton: false, index: index)
69
+ walk_children(node.body, inner, singleton: false, index: index)
69
70
  when Prism::SingletonClassNode
70
71
  walk_children(node.body, nesting, singleton: true, index: index)
71
72
  when Prism::DefNode
@@ -76,6 +77,24 @@ module Rigor
76
77
  end
77
78
  end
78
79
 
80
+ # A ROOTED header is the one case that needs no resolution to place: `::` re-anchors the declaration at
81
+ # the top level wherever it is written, exactly as `Source::ConstantPath.declaration_prefix` reads it
82
+ # for the engine's own walks. Prism's `full_name` KEEPS the `::`, so `class ::Rooted` inside
83
+ # `module Outer` was indexed under `Outer::::Rooted#m` — a key no caller spells, which cost the row
84
+ # its line while every unrooted sibling in the same file kept one
85
+ # ([#722](https://github.com/rigortype/rigor/issues/722) residue 4).
86
+ #
87
+ # nil propagates the refusal {#constant_name} makes, so a header naming no constant leaves the subtree
88
+ # unindexed rather than under a guessed name.
89
+ def header_nesting(nesting, constant_path)
90
+ name = constant_name(constant_path)
91
+ return nil if name.nil?
92
+
93
+ return nesting + [name] unless Source::ConstantPath.rooted?(constant_path)
94
+
95
+ [name.delete_prefix("::")]
96
+ end
97
+
79
98
  def walk_children(node, nesting, singleton:, index:)
80
99
  node&.compact_child_nodes&.each { |child| walk(child, nesting, singleton: singleton, index: index) }
81
100
  end
@@ -29,11 +29,19 @@ module Rigor
29
29
  # The lanes are kept **raw** here — a declared label a proven one already subsumes is dropped where
30
30
  # output is rendered ({LabelSet#excluding_subsumed_by}), never in the table, because a further join
31
31
  # has to see what was actually declared.
32
+ # `unclaimed` is the transitive reading of {Summary#unclaimed?}: true when this method, or anything
33
+ # it reaches, called something nothing described (#391). It is not a label, not a taint, and is read
34
+ # by `rigor sig-gen`'s annotation emission alone — not by the report, the snapshot, or any
35
+ # diagnostic.
32
36
  class Entry < Data.define(:key, :direct, :proven, :undischarged, :declared, :exhaustive, :causes,
33
- :edges)
34
- def initialize(undischarged: nil, declared: nil, **rest)
37
+ :edges, :unclaimed)
38
+ def initialize(undischarged: nil, declared: nil, unclaimed: false, **rest)
35
39
  super(undischarged: undischarged || rest.fetch(:proven), declared: declared || LabelSet::EMPTY,
36
- **rest)
40
+ unclaimed: unclaimed ? true : false, **rest)
41
+ end
42
+
43
+ def unclaimed?
44
+ unclaimed
37
45
  end
38
46
 
39
47
  # The declared labels worth printing beside `proven` — the rendering rule, in one place so the
@@ -61,7 +69,6 @@ module Rigor
61
69
  @empty ||= new(EMPTY_ENTRIES)
62
70
  end
63
71
 
64
- # @param entries [Hash{String => Entry}]
65
72
  def initialize(entries)
66
73
  @entries = entries.sort_by { |key, _| key }.to_h.freeze
67
74
  freeze
@@ -117,8 +117,6 @@ module Rigor
117
117
  # one message: the unregistered-preset error (#433) and the note `rigor effects update` prints
118
118
  # when `reach:` is empty (#436) have to give the same answer, and neither can hard-code a list
119
119
  # that moves with `plugins:`.
120
- #
121
- # @return [String]
122
120
  def availability
123
121
  if @presets.empty?
124
122
  return "no plugin in this project registers an entry-point preset — a preset is named by the " \
@@ -61,7 +61,7 @@ module Rigor
61
61
  # It lives on the value rather than in {EnvelopeCheck} because {LiskovCheck} positions its
62
62
  # findings identically, and two spellings of "where the fix goes" would eventually disagree.
63
63
  #
64
- # @return [Array(String, Integer)] `[path, line]`; `[nil, 1]` for a key with no owner.
64
+ # @return `[path, line]`; `[nil, 1]` for a key with no owner.
65
65
  def for(key)
66
66
  class_name, separator, selector = MethodKey.split(key)
67
67
  return [nil, 1] if class_name.nil?
@@ -103,17 +103,17 @@ module Rigor
103
103
 
104
104
  module_function
105
105
 
106
- # @param table [EffectTable] the run's propagated graph.
107
- # @param method_envelopes [Hash{String => Envelope}] per-method envelopes, as written.
108
- # @param class_envelopes [Hash{String => Envelope}] class- / module-level envelopes, to distribute.
109
- # @param config_envelopes [Hash{String => Envelope}] `effects.envelopes:` entries already resolved
106
+ # @param table — the run's propagated graph.
107
+ # @param method_envelopes — per-method envelopes, as written.
108
+ # @param class_envelopes — class- / module-level envelopes, to distribute.
109
+ # @param config_envelopes — `effects.envelopes:` entries already resolved
110
110
  # to the classes they select ({ConfigEnvelopes.for_classes}), to distribute at the lowest precedence.
111
- # @param positions [Positions, DeferredPositions] the discovery tables a finding's `def`
111
+ # @param positions — the discovery tables a finding's `def`
112
112
  # position is read from — consulted only when a finding is built, so a deferred value's
113
113
  # discovery force is reached exactly as often as a finding exists.
114
- # @param apply_tolerated [Boolean] false judges against the undischarged-by-policy `proven` lane —
114
+ # @param apply_tolerated — false judges against the undischarged-by-policy `proven` lane —
115
115
  # the `--no-tolerated-effects` audit switch.
116
- # @return [Array<Finding>] sorted by position then key then label, so a run explains identically twice.
116
+ # @return sorted by position then key then label, so a run explains identically twice.
117
117
  def run(table:, method_envelopes:, class_envelopes:, config_envelopes: {},
118
118
  positions: Positions.empty, apply_tolerated: true)
119
119
  envelopes = distribute(table, method_envelopes, class_envelopes, config_envelopes)
@@ -157,7 +157,7 @@ module Rigor
157
157
  # synthesized `attr_*` / `define_method` members the effects scanner adds.
158
158
  def keys_by_class(table)
159
159
  table.keys.each_with_object({}) do |key, out|
160
- owner = MethodKey.owner(key)
160
+ owner = MethodKey.envelope_owner(key)
161
161
  next if owner.nil?
162
162
 
163
163
  (out[owner] ||= []) << key
@@ -56,15 +56,13 @@ module Rigor
56
56
 
57
57
  # Reads every stratum this index serves, once per process.
58
58
  #
59
- # @param configuration [Rigor::Configuration]
60
- # @param plugin_facts [Rigor::Effects::PluginFacts, nil] the loaded plugins' effect contributions
59
+ # @param plugin_facts — the loaded plugins' effect contributions
61
60
  # (#387); their `effect_labels:` join the vocabulary an annotation is read against, so a gem's
62
61
  # `%a{rigor:v1:effect rails.activejob.enqueue}` resolves rather than reading as unknown.
63
- # @param environment [Rigor::Environment, nil] the run's environment. Its loader supplies the
62
+ # @param environment — the run's environment. Its loader supplies the
64
63
  # rbs-inline / plugin `virtual_rbs` buffers and the built RBS environment the accepted stratum is
65
64
  # read from; without one, both are simply absent (the fail-quiet direction — a missing `≤` bound
66
65
  # costs precision, never a finding).
67
- # @return [EnvelopeIndex]
68
66
  def self.build(configuration:, environment: nil, plugin_facts: nil)
69
67
  # Required here rather than at the top of the file: the reader pulls in the whole
70
68
  # `RbsExtended` surface, and this build runs only under an `effects:` block.
@@ -122,10 +120,9 @@ module Rigor
122
120
 
123
121
  # The envelope bounding `owner`'s `selector`, or nil.
124
122
  #
125
- # @param owner [String] the receiver's static class name, as the typer projected it
126
- # @param singleton [Boolean] whether the call is `Owner.selector` rather than `Owner#selector`
127
- # @param selector [String]
128
- # @return [Envelope, nil] never a ⊤ envelope: a bound that bounds nothing is not a bound, and
123
+ # @param owner — the receiver's static class name, as the typer projected it
124
+ # @param singleton — whether the call is `Owner.selector` rather than `Owner#selector`
125
+ # @return never a ⊤ envelope: a bound that bounds nothing is not a bound, and
129
126
  # importing it would both add nothing and discharge a taint on the strength of a typo.
130
127
  def [](owner, singleton, selector)
131
128
  return nil if owner.nil? || empty?
@@ -138,6 +135,21 @@ module Rigor
138
135
  envelope
139
136
  end
140
137
 
138
+ # Whether ANY stratum carries an annotation for this key — ⊤ envelopes included.
139
+ #
140
+ # {#[]} is the import question and rightly refuses a ⊤ envelope: a bound that bounds nothing is not
141
+ # a bound. `rigor sig-gen`'s emission asks the other one (#391): an author who wrote
142
+ # `%a{rigor:v1:effect bogus.nonsense}` has still written about this method, and replacing their
143
+ # typo with an inferred `%a{pure}` would delete the only evidence that they meant to bound it — the
144
+ # `effect.unknown-label` report points at the annotation, and there would no longer be one.
145
+ def annotated?(owner, singleton, selector)
146
+ return false if owner.nil? || empty?
147
+
148
+ key = "#{owner}#{singleton ? '.' : '#'}#{selector}"
149
+ !(@method_envelopes[key] || @class_envelopes[owner] || config_envelope(owner) ||
150
+ @accepted[key]).nil?
151
+ end
152
+
141
153
  private
142
154
 
143
155
  # The first `namespace:` entry selecting `owner`, memoised per class name — one call site's owner
@@ -35,12 +35,59 @@ module Rigor
35
35
  # selector rather than a receiver's, and the propagator resolves it against the ancestry *above* that
36
36
  # class with no closed-world override join — a different question from every other edge, which is why
37
37
  # it is a field rather than a convention over the other three.
38
- Edge = Data.define(:receiver_class, :kind, :selector, :self_call, :super_call) do
38
+ #
39
+ # `constant_receiver` marks a call whose receiver the author wrote as a **constant path** (#1039).
40
+ # The collector keys an edge on the receiver's TYPE, so `self.class.new` inside `Base`, `klass.new`
41
+ # on a `Singleton[Base]` local and a receiver-less `new` in a singleton body all produce the very
42
+ # same tuple as a literal `Base.new` — and the first three really do construct a subclass. Only the
43
+ # constructor rule reads it, and only to DROP the closed-world subclass join a written constant
44
+ # cannot reach.
45
+ #
46
+ # `unclaimed` marks a site NOTHING bounded: no catalogue row, no plugin row, no imported envelope
47
+ # (#391). It is not a taint and never becomes one — an unresolved edge here is overwhelmingly an
48
+ # inherited or gem call the catalogue simply has no row for, which is why the model drops it — but
49
+ # it IS the difference between "the analyzer read every callee" and "the analyzer read the callees
50
+ # it had something to read". Only sig-gen's annotation emission consults it, through
51
+ # {EffectTable::Entry#unclaimed?}: writing `%a{pure}` is a claim about a callee nobody described,
52
+ # and the emitter must decline rather than invent one.
53
+ # `taint_if_unresolved` is #1048's inversion of a taint. A `render :show` whose {CalleeRule} named
54
+ # `view:users/show.html` may or may not reach a unit — only the merged table knows, exactly as only
55
+ # the merged ancestry knows whether a `super` resolves — so the row's `template-not-analysed` cause
56
+ # travels ON the edge as a frozen `[cause, detail]` pair and {Propagator} seeds it only where the
57
+ # edge landed on nothing. Adding a cause where the walk failed rather than subtracting one where it
58
+ # succeeded keeps every step of the fixpoint monotone, and it is the same shape
59
+ # {Propagator.taint_unresolved_super} already has.
60
+ #
61
+ # `fallback_selectors` is #1065's lookup order: the selectors the propagator retries, in order, when
62
+ # `selector` resolves to nothing — `["html"]` on a `view:watchers/_watchers` + `js` edge, because a
63
+ # `.js.erb` template's lookup context is `[:js, :html]`. The FIRST that resolves is the edge's
64
+ # target and the taint stays unseeded; only when every one fails is `taint_if_unresolved` seeded.
65
+ # One ordered list rather than one edge per candidate, because two edges would join BOTH units
66
+ # where both exist, and only one of them runs.
67
+ Edge = Data.define(:receiver_class, :kind, :selector, :self_call, :super_call, :unclaimed,
68
+ :constant_receiver, :taint_if_unresolved, :fallback_selectors) do
39
69
  # Defaulted because every producer but the `super` one records an ordinary call, and an ordinary
40
- # call is not a `super`.
41
- def initialize(super_call: false, **) = super
70
+ # call is not a `super`, and because a producer that says nothing about `unclaimed` /
71
+ # `constant_receiver` means the safe value of each.
72
+ #
73
+ # The defaults do NOT rescue a cache written before a member existed: `Marshal.load` of a `Data`
74
+ # whose member list has grown raises `TypeError: struct size differs`, which the store reads as a
75
+ # miss. That is the right outcome and not the one relied on — the cache identity carries a schema
76
+ # component ({Identity}, `schema:5`), so such an entry is never offered in the first place.
77
+ def initialize(super_call: false, unclaimed: false, constant_receiver: false,
78
+ taint_if_unresolved: nil, fallback_selectors: nil, **)
79
+ super
80
+ end
42
81
  end
43
82
 
83
+ # An ancestry entry that names nothing and can never resolve. The scanner records it where a class
84
+ # body says its ancestry or its constructor is not readable from the source — a superclass
85
+ # expression that is not a constant path (`class K < Struct.new(:a)`), or an `alias` / `alias_method`
86
+ # that makes `initialize` some other method (#1039). It matches no summary key, so every existing
87
+ # walk steps over it; the constructor rule asks for it by name, because "unreadable" is exactly what
88
+ # that rule must not read as "empty".
89
+ OPAQUE_ANCESTOR = "<opaque>"
90
+
44
91
  NO_TABLE = {}.freeze
45
92
  private_constant :NO_TABLE
46
93
 
@@ -107,7 +154,12 @@ module Rigor
107
154
 
108
155
  fold_summaries(summaries, collection.summaries)
109
156
  fold_lists(edges, collection.edges)
110
- superclasses.update(collection.superclasses)
157
+ # The opaque sentinel is sticky across files as well as within one: a class whose constructor one
158
+ # file says is unreadable does not become readable because another file spells its `<`, and the
159
+ # fold must not depend on which file the run reads first.
160
+ superclasses.update(collection.superclasses) do |_key, mine, theirs|
161
+ mine.include?(OPAQUE_ANCESTOR) ? mine : theirs
162
+ end
111
163
  fold_lists(includes, collection.includes)
112
164
  end
113
165
 
@@ -167,7 +219,9 @@ module Rigor
167
219
  end
168
220
 
169
221
  def edge_order(edge)
170
- [edge.receiver_class.to_s, edge.kind.to_s, edge.selector, edge.self_call ? 1 : 0, edge.super_call ? 1 : 0]
222
+ [edge.receiver_class.to_s, edge.kind.to_s, edge.selector, edge.self_call ? 1 : 0,
223
+ edge.super_call ? 1 : 0, edge.constant_receiver ? 1 : 0,
224
+ Array(edge.taint_if_unresolved).join("\x00"), Array(edge.fallback_selectors).join("\x00")]
171
225
  end
172
226
  end
173
227
  end
@@ -76,13 +76,11 @@ module Rigor
76
76
 
77
77
  # Every synthetic unit `class_name` earns, as `[key, Summary, edges]` triples.
78
78
  #
79
- # @param class_name [String]
80
- # @param instance_methods [Array<String>] the instance methods the class body defines, in source order
81
- # @param macros [Hash{String=>Array<String>}] receiver-less class-body calls to their literal symbol
79
+ # @param instance_methods — the instance methods the class body defines, in source order
80
+ # @param macros — receiver-less class-body calls to their literal symbol
82
81
  # arguments, as the scanner harvested them
83
- # @param uniqueness [Boolean] whether the class body declares a uniqueness validator
84
- # @param plugin_facts [PluginFacts]
85
- # @param own_units [Hash{String=>Boolean}] the units the class body itself defines, keyed by the
82
+ # @param uniqueness — whether the class body declares a uniqueness validator
83
+ # @param own_units — the units the class body itself defines, keyed by the
86
84
  # suffix a synthetic key carries (`"#save"`, `".create"`), each mapped to whether that body
87
85
  # reaches `super`. Read by {.framework_row} and by nothing else.
88
86
  def synthesize(class_name:, instance_methods:, macros:, uniqueness:, plugin_facts:, own_units: {})
@@ -198,7 +196,7 @@ module Rigor
198
196
  end
199
197
 
200
198
  # The uniqueness validator's own query. It rides the DECLARED lane with no taint, exactly as every
201
- # other first-party plugin contribution does (ADR-103 WD6): the plugin read the app's own
199
+ # other first-party plugin contribution does (ADR-103 WD6 / WD17): the plugin read the app's own
202
200
  # `validates … uniqueness: true` and knows what Rails does with it, but the analyzer did not read a
203
201
  # body, so this is a trusted claim rather than a proof.
204
202
  def uniqueness_summary(class_name)
@@ -45,13 +45,28 @@ module Rigor
45
45
  # The effects identity as a hex digest — the form a store with no descriptor of its own (the ADR-46
46
46
  # incremental snapshot) carries alongside its payload, and compares verbatim on restore.
47
47
  #
48
- # @param configuration [Rigor::Configuration]
49
- # @param registry [Registry] the vocabulary whose version participates
50
- # @param catalog [Catalog] the catalogue whose identity participates
51
- # @return [String] hex SHA-256
48
+ # @param registry — the vocabulary whose version participates
49
+ # @param catalog — the catalogue whose identity participates
50
+ # @return hex SHA-256
52
51
  def digest(configuration:, registry: Registry.default, catalog: Catalog.default, plugin_facts: nil)
53
52
  Digest::SHA256.hexdigest(
54
53
  [
54
+ # #391 — the summary schema itself. A cached collection written before {Summary#unclaimed?}
55
+ # existed restores with the bit false, which is the direction sig-gen's emission must never
56
+ # guess, so such an entry must not be served rather than merely read carefully.
57
+ #
58
+ # schema:3 — #1039 moved edge RESOLUTION: a singleton `new` edge on a project class now
59
+ # reaches that class's `#initialize`. The warm lanes serve a propagated `EffectTable` (and its
60
+ # `unclaimed` bits) rather than re-resolving, so an entry written under the old rule would
61
+ # answer the old closure for source nothing changed.
62
+ #
63
+ # schema:4 — #1048 added {FileCollection::Edge#taint_if_unresolved}, so a marshalled edge
64
+ # written under schema:3 no longer restores at all (`Data` with a grown member list raises
65
+ # `TypeError`), and a warm entry would otherwise be offered and then read as a miss.
66
+ #
67
+ # schema:5 — #1065 added {FileCollection::Edge#fallback_selectors}, which both grows the
68
+ # marshalled member list again and changes which unit a cached `view:` edge resolves to.
69
+ "schema:5",
55
70
  "vocabulary:#{registry.vocabulary_version}",
56
71
  "catalog:#{catalog.identity}",
57
72
  "effects:#{config_digest(configuration)}",
@@ -66,8 +81,7 @@ module Rigor
66
81
  # the analyzed-path set — for free, so "the effects identity is the diagnostics identity plus three
67
82
  # things" is a property of the code rather than a claim about it.
68
83
  #
69
- # @param base [Cache::Descriptor] the run's diagnostics key descriptor
70
- # @return [Cache::Descriptor]
84
+ # @param base — the run's diagnostics key descriptor
71
85
  def descriptor(base:, configuration:, registry: Registry.default, catalog: Catalog.default,
72
86
  plugin_facts: nil)
73
87
  Cache::Descriptor.compose(
@@ -49,9 +49,9 @@ module Rigor
49
49
  SPELLING_PATTERN = /%a\{[^}]*\}/
50
50
  private_constant :SPELLING_PATTERN
51
51
 
52
- # @param path [String] the buffer's readable path, as {SignatureSources.source_path} renders it.
53
- # @param buffer [String] the synthesized RBS the position was read out of.
54
- # @return [InlineAnchor, nil] nil when `path` is not a Ruby file — a real `.rbs` needs no mapping,
52
+ # @param path — the buffer's readable path, as {SignatureSources.source_path} renders it.
53
+ # @param buffer — the synthesized RBS the position was read out of.
54
+ # @return nil when `path` is not a Ruby file — a real `.rbs` needs no mapping,
55
55
  # and its own line numbers are already the ones a reader can open.
56
56
  def self.for(path:, buffer:)
57
57
  return nil unless path.to_s.end_with?(RUBY_EXTENSION)
@@ -61,7 +61,7 @@ module Rigor
61
61
 
62
62
  # One-shot form, for a caller that holds a `[path, buffer, line]` triple and no anchor.
63
63
  #
64
- # @return [Integer] the `.rb` line, or `buffer_line` unchanged when there is nothing to map.
64
+ # @return the `.rb` line, or `buffer_line` unchanged when there is nothing to map.
65
65
  def self.ruby_line(path:, buffer:, buffer_line:, spelling: nil)
66
66
  anchor = self.for(path: path, buffer: buffer)
67
67
  return buffer_line if anchor.nil?
@@ -75,10 +75,10 @@ module Rigor
75
75
  @ruby_lines = nil
76
76
  end
77
77
 
78
- # @param buffer_line [Integer] 1-based, counting into the synthesized buffer.
79
- # @param spelling [String, nil] the annotation's own text (`"%a{pure}"`); read off the buffer line
78
+ # @param buffer_line — 1-based, counting into the synthesized buffer.
79
+ # @param spelling — the annotation's own text (`"%a{pure}"`); read off the buffer line
80
80
  # when the caller does not already hold it.
81
- # @return [Integer] the 1-based line of the same annotation in the Ruby file.
81
+ # @return the 1-based line of the same annotation in the Ruby file.
82
82
  def line_for(buffer_line, spelling: nil)
83
83
  spelling = normalize(spelling) || spelling_at(buffer_line)
84
84
  return buffer_line if spelling.nil?
@@ -37,11 +37,10 @@ module Rigor
37
37
 
38
38
  # Whether reporting `token` as an unknown label is justified.
39
39
  #
40
- # @param token [String] the spelling as written.
41
- # @param registry [Rigor::Effects::Registry, nil] the vocabulary AFTER plugin load; `nil` (no
40
+ # @param token — the spelling as written.
41
+ # @param registry — the vocabulary AFTER plugin load; `nil` (no
42
42
  # vocabulary at all) makes every token unjudgeable and therefore silent.
43
- # @param siblings [Array<String>] the other tokens of the same list / the same config value.
44
- # @return [Boolean]
43
+ # @param siblings — the other tokens of the same list / the same config value.
45
44
  def evident?(token, registry, siblings: [])
46
45
  return false if registry.nil?
47
46
  return false unless Label.valid?(token)
@@ -56,16 +56,16 @@ module Rigor
56
56
 
57
57
  module_function
58
58
 
59
- # @param table [EffectTable] the run's propagated graph.
60
- # @param superclasses [Hash{String => Array<String>}] the collector's as-written superclass
59
+ # @param table — the run's propagated graph.
60
+ # @param superclasses — the collector's as-written superclass
61
61
  # candidate lists (`FileCollection#superclasses`).
62
- # @param method_envelopes [Hash{String => Envelope}] per-method envelopes, as written.
63
- # @param class_envelopes [Hash{String => Envelope}] class- / module-level envelopes, to distribute.
64
- # @param config_envelopes [Hash{String => Envelope}] `effects.envelopes:` entries already resolved
62
+ # @param method_envelopes — per-method envelopes, as written.
63
+ # @param class_envelopes — class- / module-level envelopes, to distribute.
64
+ # @param config_envelopes — `effects.envelopes:` entries already resolved
65
65
  # to the classes they select.
66
- # @param positions [EnvelopeCheck::Positions] where the override's `def` is.
67
- # @param apply_tolerated [Boolean] false judges against `proven` — `--no-tolerated-effects`.
68
- # @return [Array<Finding>] sorted by position then key then label.
66
+ # @param positions — where the override's `def` is.
67
+ # @param apply_tolerated — false judges against `proven` — `--no-tolerated-effects`.
68
+ # @return sorted by position then key then label.
69
69
  def run(table:, superclasses:, method_envelopes:, class_envelopes:, config_envelopes: {},
70
70
  positions: EnvelopeCheck::Positions.empty, apply_tolerated: true)
71
71
  # The distributed strata, over a base of the raw per-method annotations: a base class whose method
@@ -11,8 +11,8 @@ module Rigor
11
11
  #
12
12
  # Ruby has no by-ref parameters, so `mutate.local` cannot mean "a write into an out-parameter" as it
13
13
  # does in PHP. It means the mutated receiver is invisible to the caller, and that is an ownership
14
- # question: a local whose every assignment allocates (`[]`, `{}`, `""`, `.new`, `.dup`) and which never
15
- # escapes the body is one no caller can observe being mutated.
14
+ # question: a local whose every assignment allocates (`[]`, `{}`, `""`, `Foo.new`, `.dup`) and which
15
+ # never escapes the body is one no caller can observe being mutated.
16
16
  #
17
17
  # The analysis is deliberately **flow-insensitive and whole-body**: a local that escapes anywhere
18
18
  # disqualifies, even if the escape happens after the mutation. That is strictly more conservative than
@@ -23,16 +23,17 @@ module Rigor
23
23
  # This is the tracer slice's approximation, not the eventual answer. `ClosureEscapeAnalyzer` answers a
24
24
  # different question (fact retention, not "does the code contain") and is deliberately left alone.
25
25
  module LocalOwnership
26
- # Assignment right-hand sides that witness a fresh allocation. `.new` and `.dup` / `.clone` follow
26
+ # Assignment right-hand sides that witness a fresh allocation. `.dup` / `.clone` follow
27
27
  # [ADR-76](../adr/76-effect-modeling-freeze-dup-shape-preservation.md)'s reading of `dup` as the
28
- # allocation witness.
29
- ALLOCATING_SELECTORS = %i[new dup clone].to_set.freeze
28
+ # allocation witness; `.new` is {constructor?}'s, because only a class object's `new` is `Class#new`.
29
+ COPYING_SELECTORS = %i[dup clone].to_set.freeze
30
30
 
31
31
  module_function
32
32
 
33
33
  # The set of frame-owned local names in `body`, given the method's parameter names (a parameter is
34
- # never frame-owned — the caller holds the same object, so mutating it is `mutate.instance`).
35
- def owned(body, parameter_names)
34
+ # never frame-owned — the caller holds the same object, so mutating it is `mutate.instance`) and
35
+ # whether `self` is a class there ({constructor?}).
36
+ def owned(body, parameter_names, singleton:)
36
37
  return Set.new if body.nil?
37
38
 
38
39
  assignments = {}
@@ -42,23 +43,48 @@ module Rigor
42
43
  assignments.filter_map do |name, values|
43
44
  next if escaped.include?(name) || parameter_names.include?(name)
44
45
 
45
- name if values.all? { |value| allocation?(value) }
46
+ name if values.all? { |value| allocation?(value, singleton: singleton) }
46
47
  end.to_set
47
48
  end
48
49
 
49
50
  # Whether `node` is an expression that allocates a fresh object this frame is the sole holder of.
50
- def allocation?(node)
51
+ # `singleton` is whether the enclosing unit's `self` is a class, as {constructor?} reads it.
52
+ def allocation?(node, singleton:)
51
53
  case node
52
54
  when Prism::ArrayNode, Prism::HashNode, Prism::StringNode, Prism::InterpolatedStringNode,
53
55
  Prism::LambdaNode
54
56
  true
55
57
  when Prism::CallNode
56
- ALLOCATING_SELECTORS.include?(node.name) || unary_plus_string?(node)
58
+ COPYING_SELECTORS.include?(node.name) || constructor?(node, singleton: singleton) ||
59
+ unary_plus_string?(node)
57
60
  else
58
61
  false
59
62
  end
60
63
  end
61
64
 
65
+ # A `new` whose receiver the author wrote as a class object: a constant path, a `class` call
66
+ # (`self.class`, `other.class`), or `self` — explicit or implicit — in a singleton-method body. Any
67
+ # other receiver may be an object whose `new` only shares the name: an ActiveRecord association's
68
+ # `new` builds a record into the association's own target, where the caller can reach it, and a
69
+ # gem's method gives no edge that would carry that to the caller. `self` in an instance method is
70
+ # such a receiver too — an association extension's `new` is the association's.
71
+ def constructor?(node, singleton:)
72
+ return false unless node.name == :new
73
+
74
+ receiver = node.receiver
75
+ case receiver
76
+ when nil, Prism::SelfNode then singleton
77
+ when Prism::ConstantReadNode, Prism::ConstantPathNode then true
78
+ when Prism::CallNode then class_call?(receiver)
79
+ else false
80
+ end
81
+ end
82
+
83
+ # `x.class` — `Kernel#class` answers the receiver's class, unless the receiver overrides it.
84
+ def class_call?(node)
85
+ node.name == :class && node.arguments.nil? && node.block.nil?
86
+ end
87
+
62
88
  # `+""` — the frozen-string-literal era's spelling of "a fresh mutable String".
63
89
  def unary_plus_string?(node)
64
90
  node.name == :+@ && node.receiver.is_a?(Prism::StringNode)
@@ -125,8 +151,8 @@ module Rigor
125
151
  last.is_a?(Prism::LocalVariableReadNode) ? [last.name.to_s] : []
126
152
  end
127
153
 
128
- private_class_method :collect, :record_assignment, :record_escapes, :stored_value, :note_read,
129
- :trailing_reads
154
+ private_class_method :class_call?, :collect, :record_assignment, :record_escapes, :stored_value,
155
+ :note_read, :trailing_reads
130
156
  end
131
157
  end
132
158
  end
@@ -12,9 +12,14 @@ module Rigor
12
12
  # key, and `effects.attribution:` in `.rigor.yml` names one. A key the loader accepts and the scanner
13
13
  # would never produce is a table that silently matches nothing.
14
14
  module MethodKey
15
+ # #393 — the prefix a template unit's key carries (`Plugin::TemplateUnit::KEY_PREFIX`, repeated here
16
+ # rather than required so the effects layer keeps no dependency on the plugin layer; the two are
17
+ # pinned equal by a spec).
18
+ TEMPLATE_UNIT_PREFIX = "view:"
19
+
15
20
  module_function
16
21
 
17
- # @return [Array(String, String, String), nil] `[owner, separator, selector]`, or nil when `key` is
22
+ # @return `[owner, separator, selector]`, or nil when `key` is
18
23
  # not a method key at all.
19
24
  def split(key)
20
25
  text = key.to_s
@@ -35,6 +40,22 @@ module Rigor
35
40
  def owner(key)
36
41
  split(key)&.first
37
42
  end
43
+
44
+ # #393 — a template unit's key: `view:users/show.html`. Deliberately NOT a method key — a view has
45
+ # no owner class and no selector — but {.split} cannot tell, because the format segment gives the
46
+ # string a dot: `owner("view:users/show.html")` answers `"view:users/show"`, a class name no run
47
+ # ever produced. Anything grouping keys by their envelope-bearing owner has to ask this first.
48
+ def template_unit?(key)
49
+ key.to_s.start_with?(TEMPLATE_UNIT_PREFIX)
50
+ end
51
+
52
+ # The name an `effects.envelopes:` entry selects a key by: the owner class for a method key, and the
53
+ # unit key ITSELF for a template unit. A view is its own envelope subject — there is no class to
54
+ # hang the bound on, and `Runner#effect_sources` already knows which file the key came from, which
55
+ # is what a `match:` entry needs.
56
+ def envelope_owner(key)
57
+ template_unit?(key) ? key.to_s : owner(key)
58
+ end
38
59
  end
39
60
  end
40
61
  end