rigortype 0.2.7 → 0.2.8

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 (414) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/manual/04-diagnostics.md +7 -4
  4. data/docs/manual/06-baseline.md +5 -2
  5. data/docs/manual/15-type-protection-coverage.md +6 -4
  6. data/lib/rigor/analysis/baseline.rb +56 -96
  7. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  8. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  9. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  10. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  11. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  12. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  13. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  14. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  15. data/lib/rigor/analysis/check_rules.rb +174 -274
  16. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  17. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  18. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  19. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  20. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  21. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  22. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  23. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  24. data/lib/rigor/analysis/diagnostic.rb +48 -75
  25. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  26. data/lib/rigor/analysis/fact_store.rb +12 -22
  27. data/lib/rigor/analysis/incremental.rb +47 -63
  28. data/lib/rigor/analysis/incremental_session.rb +74 -100
  29. data/lib/rigor/analysis/project_scan.rb +12 -23
  30. data/lib/rigor/analysis/result.rb +3 -5
  31. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  32. data/lib/rigor/analysis/run_stats.rb +38 -61
  33. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  34. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  35. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  36. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  37. data/lib/rigor/analysis/runner.rb +220 -333
  38. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  39. data/lib/rigor/analysis/worker_session.rb +64 -102
  40. data/lib/rigor/ast/type_node.rb +6 -8
  41. data/lib/rigor/ast.rb +6 -10
  42. data/lib/rigor/bleeding_edge.rb +27 -35
  43. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  44. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  45. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  46. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  47. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  48. data/lib/rigor/cache/descriptor.rb +58 -94
  49. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  50. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  51. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  52. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  53. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  54. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  55. data/lib/rigor/cache/rbs_environment.rb +11 -19
  56. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  57. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  58. data/lib/rigor/cache/store.rb +96 -151
  59. data/lib/rigor/cli/annotate_command.rb +51 -88
  60. data/lib/rigor/cli/baseline_command.rb +10 -17
  61. data/lib/rigor/cli/check_command.rb +121 -199
  62. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  63. data/lib/rigor/cli/ci_detector.rb +9 -14
  64. data/lib/rigor/cli/command.rb +8 -14
  65. data/lib/rigor/cli/coverage_command.rb +19 -26
  66. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  67. data/lib/rigor/cli/coverage_report.rb +0 -2
  68. data/lib/rigor/cli/coverage_scan.rb +7 -11
  69. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  70. data/lib/rigor/cli/diff_command.rb +10 -20
  71. data/lib/rigor/cli/docs_command.rb +26 -40
  72. data/lib/rigor/cli/doctor_command.rb +8 -9
  73. data/lib/rigor/cli/explain_command.rb +4 -7
  74. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  75. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  76. data/lib/rigor/cli/lsp_command.rb +11 -19
  77. data/lib/rigor/cli/mcp_command.rb +4 -6
  78. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  79. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  80. data/lib/rigor/cli/options.rb +11 -19
  81. data/lib/rigor/cli/plugin_command.rb +18 -30
  82. data/lib/rigor/cli/plugins_command.rb +29 -51
  83. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  84. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  85. data/lib/rigor/cli/protection_renderer.rb +5 -7
  86. data/lib/rigor/cli/protection_report.rb +27 -17
  87. data/lib/rigor/cli/renderable.rb +4 -6
  88. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  89. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  90. data/lib/rigor/cli/skill_command.rb +30 -47
  91. data/lib/rigor/cli/skill_describe.rb +40 -64
  92. data/lib/rigor/cli/trace_command.rb +9 -16
  93. data/lib/rigor/cli/trace_renderer.rb +35 -51
  94. data/lib/rigor/cli/triage_command.rb +6 -10
  95. data/lib/rigor/cli/triage_renderer.rb +1 -2
  96. data/lib/rigor/cli/type_of_command.rb +14 -23
  97. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  98. data/lib/rigor/cli/type_scan_command.rb +6 -10
  99. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_report.rb +2 -3
  101. data/lib/rigor/cli/upgrade_command.rb +2 -3
  102. data/lib/rigor/cli.rb +16 -28
  103. data/lib/rigor/config_audit.rb +35 -44
  104. data/lib/rigor/configuration/dependencies.rb +41 -70
  105. data/lib/rigor/configuration/severity_profile.rb +28 -42
  106. data/lib/rigor/configuration.rb +150 -241
  107. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  108. data/lib/rigor/environment/class_registry.rb +19 -26
  109. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  110. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  111. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  112. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  113. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  114. data/lib/rigor/environment/rbs_loader.rb +420 -518
  115. data/lib/rigor/environment/reflection.rb +28 -53
  116. data/lib/rigor/environment/reporters.rb +13 -24
  117. data/lib/rigor/environment.rb +176 -291
  118. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  119. data/lib/rigor/flow_contribution/element.rb +7 -12
  120. data/lib/rigor/flow_contribution/fact.rb +33 -57
  121. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  122. data/lib/rigor/flow_contribution/merger.rb +32 -47
  123. data/lib/rigor/flow_contribution.rb +37 -55
  124. data/lib/rigor/inference/acceptance.rb +133 -219
  125. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  126. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  127. data/lib/rigor/inference/budget_trace.rb +43 -63
  128. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  129. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  130. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  131. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  132. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  133. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  134. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  135. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  136. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  137. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  138. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  139. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  140. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  141. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  142. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  143. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  144. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  145. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  146. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  147. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  148. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  149. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  150. data/lib/rigor/inference/def_return_typer.rb +8 -14
  151. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  152. data/lib/rigor/inference/expression_typer.rb +774 -1152
  153. data/lib/rigor/inference/fallback.rb +8 -12
  154. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  155. data/lib/rigor/inference/flow_tracer.rb +25 -36
  156. data/lib/rigor/inference/hkt_body.rb +45 -68
  157. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  158. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  159. data/lib/rigor/inference/hkt_registry.rb +50 -76
  160. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  161. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  162. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  163. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  164. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  165. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  166. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  167. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  168. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  169. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  170. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  171. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  172. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  173. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  174. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  175. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  176. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  177. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  178. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  179. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  180. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  181. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  182. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  183. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  184. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  185. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  186. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  187. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  188. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  189. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  190. data/lib/rigor/inference/mutation_widening.rb +115 -158
  191. data/lib/rigor/inference/narrowing.rb +509 -727
  192. data/lib/rigor/inference/origin_lookup.rb +38 -0
  193. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  194. data/lib/rigor/inference/precision_scanner.rb +26 -35
  195. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  196. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  197. data/lib/rigor/inference/protection_scanner.rb +16 -18
  198. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  199. data/lib/rigor/inference/scope_indexer.rb +473 -817
  200. data/lib/rigor/inference/statement_evaluator.rb +706 -1111
  201. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  202. data/lib/rigor/inference/synthetic_method.rb +9 -16
  203. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  204. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  205. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  206. data/lib/rigor/language_server/buffer_table.rb +11 -18
  207. data/lib/rigor/language_server/completion_provider.rb +69 -116
  208. data/lib/rigor/language_server/debouncer.rb +16 -25
  209. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  210. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  211. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  212. data/lib/rigor/language_server/hover_provider.rb +8 -11
  213. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  214. data/lib/rigor/language_server/loop.rb +14 -19
  215. data/lib/rigor/language_server/project_context.rb +46 -74
  216. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  217. data/lib/rigor/language_server/server.rb +55 -83
  218. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  219. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  220. data/lib/rigor/language_server/uri.rb +8 -13
  221. data/lib/rigor/language_server.rb +4 -6
  222. data/lib/rigor/mcp/loop.rb +2 -3
  223. data/lib/rigor/mcp/server.rb +4 -7
  224. data/lib/rigor/mcp.rb +3 -6
  225. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  226. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  227. data/lib/rigor/plugin/base.rb +335 -518
  228. data/lib/rigor/plugin/blueprint.rb +14 -23
  229. data/lib/rigor/plugin/box.rb +18 -29
  230. data/lib/rigor/plugin/fact_store.rb +16 -26
  231. data/lib/rigor/plugin/inflector.rb +37 -53
  232. data/lib/rigor/plugin/io_boundary.rb +33 -56
  233. data/lib/rigor/plugin/isolation.rb +42 -55
  234. data/lib/rigor/plugin/load_error.rb +10 -15
  235. data/lib/rigor/plugin/loader.rb +30 -49
  236. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  237. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  238. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  239. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  240. data/lib/rigor/plugin/macro.rb +10 -15
  241. data/lib/rigor/plugin/manifest.rb +85 -144
  242. data/lib/rigor/plugin/node_context.rb +14 -22
  243. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  244. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  245. data/lib/rigor/plugin/registry.rb +132 -205
  246. data/lib/rigor/plugin/services.rb +21 -33
  247. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  248. data/lib/rigor/plugin/trust_policy.rb +24 -38
  249. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  250. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  251. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  252. data/lib/rigor/protection/mutator.rb +50 -70
  253. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  254. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  255. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  256. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  257. data/lib/rigor/rbs_extended.rb +107 -197
  258. data/lib/rigor/reflection.rb +68 -86
  259. data/lib/rigor/scope/discovery_index.rb +14 -19
  260. data/lib/rigor/scope.rb +255 -310
  261. data/lib/rigor/sig_gen/classification.rb +6 -10
  262. data/lib/rigor/sig_gen/generator.rb +197 -323
  263. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  264. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  265. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  266. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  267. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  268. data/lib/rigor/sig_gen/renderer.rb +7 -13
  269. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  270. data/lib/rigor/sig_gen/write_result.rb +8 -16
  271. data/lib/rigor/sig_gen/writer.rb +95 -174
  272. data/lib/rigor/sig_gen.rb +3 -6
  273. data/lib/rigor/signature_path_audit.rb +24 -30
  274. data/lib/rigor/source/constant_path.rb +10 -14
  275. data/lib/rigor/source/literals.rb +31 -45
  276. data/lib/rigor/source/node_locator.rb +9 -11
  277. data/lib/rigor/source/node_walker.rb +9 -13
  278. data/lib/rigor/source.rb +3 -4
  279. data/lib/rigor/testing.rb +16 -20
  280. data/lib/rigor/triage/catalogue.rb +38 -62
  281. data/lib/rigor/triage.rb +31 -52
  282. data/lib/rigor/trinary.rb +9 -13
  283. data/lib/rigor/type/acceptance_router.rb +4 -6
  284. data/lib/rigor/type/accepts_result.rb +10 -14
  285. data/lib/rigor/type/app.rb +19 -27
  286. data/lib/rigor/type/bot.rb +4 -6
  287. data/lib/rigor/type/bound_method.rb +10 -15
  288. data/lib/rigor/type/combinator.rb +165 -257
  289. data/lib/rigor/type/constant.rb +23 -34
  290. data/lib/rigor/type/data_class.rb +10 -15
  291. data/lib/rigor/type/data_instance.rb +14 -20
  292. data/lib/rigor/type/difference.rb +21 -32
  293. data/lib/rigor/type/dynamic.rb +3 -5
  294. data/lib/rigor/type/hash_shape.rb +32 -18
  295. data/lib/rigor/type/integer_range.rb +11 -16
  296. data/lib/rigor/type/intersection.rb +27 -42
  297. data/lib/rigor/type/nominal.rb +10 -15
  298. data/lib/rigor/type/plain_lattice.rb +9 -13
  299. data/lib/rigor/type/refined.rb +67 -114
  300. data/lib/rigor/type/singleton.rb +4 -6
  301. data/lib/rigor/type/struct_class.rb +11 -16
  302. data/lib/rigor/type/struct_instance.rb +15 -21
  303. data/lib/rigor/type/tuple.rb +14 -19
  304. data/lib/rigor/type/union.rb +30 -42
  305. data/lib/rigor/type_node/generic.rb +14 -26
  306. data/lib/rigor/type_node/identifier.rb +12 -19
  307. data/lib/rigor/type_node.rb +3 -12
  308. data/lib/rigor/value_semantics.rb +16 -21
  309. data/lib/rigor/version.rb +1 -1
  310. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  311. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  314. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  315. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  318. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  319. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
  322. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  323. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  326. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  327. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
  332. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  333. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  334. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  337. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  338. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  339. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  340. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  341. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  342. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  343. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  344. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  345. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  346. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  347. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  348. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  349. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  350. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  351. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  352. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  353. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  354. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  357. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  358. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  359. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  360. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  361. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  362. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  363. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  364. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  365. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  366. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  369. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  370. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  371. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  374. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  375. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  381. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  382. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  383. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  389. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  390. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  392. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  393. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  394. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  395. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  398. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  399. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  400. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  401. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  409. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  410. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  411. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  412. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  413. data/sig/rigor/scope.rbs +6 -0
  414. metadata +2 -1
@@ -2,26 +2,21 @@
2
2
 
3
3
  module Rigor
4
4
  module Analysis
5
- # ADR-46 slice 1 — records, per analyzed file, which OTHER source
6
- # files its analysis read declarations / method bodies from (the
7
- # cross-file dependency edges), plus the cross-file lookups that
8
- # resolved to nothing (negative edges — adding that symbol later must
9
- # re-check the consumer).
5
+ # ADR-46 slice 1 — records, per analyzed file, which OTHER source files its analysis read declarations /
6
+ # method bodies from (the cross-file dependency edges), plus the cross-file lookups that resolved to
7
+ # nothing (negative edges adding that symbol later must re-check the consumer).
10
8
  #
11
- # Thread-local and activated per `analyze_file` only when the runner
12
- # opts in (`record_dependencies: true`); a normal run never activates
13
- # it, so {active?} is a single nil-check and the instrumented `Scope`
14
- # accessors pay nothing. Recording is purely observational — it never
15
- # changes a diagnostic.
9
+ # Thread-local and activated per `analyze_file` only when the runner opts in (`record_dependencies: true`);
10
+ # a normal run never activates it, so {active?} is a single nil-check and the instrumented `Scope`
11
+ # accessors pay nothing. Recording is purely observational it never changes a diagnostic.
16
12
  #
17
- # Modelled on {Inference::BudgetTrace}: process-thread-local state, a
18
- # cheap disabled fast path, and a frozen snapshot for consumers.
13
+ # Modelled on {Inference::BudgetTrace}: process-thread-local state, a cheap disabled fast path, and a
14
+ # frozen snapshot for consumers.
19
15
  module DependencyRecorder
20
16
  KEY = :__rigor_dependency_recorder__
21
17
  private_constant :KEY
22
18
 
23
- # Mutable per-consumer accumulator. Frozen into a {Record} snapshot
24
- # when `record_for` returns.
19
+ # Mutable per-consumer accumulator. Frozen into a {Record} snapshot when `record_for` returns.
25
20
  class Accumulator
26
21
  attr_reader :consumer, :sources, :missing, :symbol_sources, :ancestry_sources
27
22
 
@@ -31,8 +26,8 @@ module Rigor
31
26
  @missing = Set.new
32
27
  # ADR-46 slice 4 — symbol-granularity tracking.
33
28
  # `symbol_sources`: source_path → Set<"ClassName#method"> for method-call deps.
34
- # `ancestry_sources`: Set<source_path> for class-ancestry (superclass / include)
35
- # deps — file-granularity by nature (a superclass edge touches the whole class).
29
+ # `ancestry_sources`: Set<source_path> for class-ancestry (superclass / include) deps —
30
+ # file-granularity by nature (a superclass edge touches the whole class).
36
31
  @symbol_sources = Hash.new { |h, k| h[k] = Set.new }
37
32
  @ancestry_sources = Set.new
38
33
  end
@@ -54,22 +49,19 @@ module Rigor
54
49
  # `ancestry_sources`: frozen Set<source_path> (class-ancestry edges, file-granularity).
55
50
  Record = Data.define(:consumer, :sources, :missing, :symbol_sources, :ancestry_sources)
56
51
 
57
- # Module-level activation count so the disabled fast path
58
- # ({active?}) is a plain integer read rather than a `Thread.current`
59
- # hash lookup `user_def_for` (the instrumented accessor) is on the
60
- # per-dispatch hot path, so a normal (non-recording) run must pay as
61
- # little as possible. The per-thread accumulator still isolates the
62
- # actual recording, so a non-recording thread seeing `active?` true
52
+ # Module-level activation count so the disabled fast path ({active?}) is a plain integer read rather
53
+ # than a `Thread.current` hash lookup `user_def_for` (the instrumented accessor) is on the
54
+ # per-dispatch hot path, so a normal (non-recording) run must pay as little as possible. The per-thread
55
+ # accumulator still isolates the actual recording, so a non-recording thread seeing `active?` true
63
56
  # (another thread is recording) just performs an extra nil-check.
64
57
  @active_count = 0
65
58
  @mutex = Mutex.new
66
59
 
67
60
  module_function
68
61
 
69
- # Activates recording for `consumer` (the path being analyzed) for
70
- # the duration of the block and returns the frozen {Record}. Nests
71
- # safely (the inner consumer's reads do not leak to the outer one);
72
- # restores the previous recorder on exit.
62
+ # Activates recording for `consumer` (the path being analyzed) for the duration of the block and returns
63
+ # the frozen {Record}. Nests safely (the inner consumer's reads do not leak to the outer one); restores
64
+ # the previous recorder on exit.
73
65
  def record_for(consumer)
74
66
  previous = Thread.current[KEY]
75
67
  accumulator = Accumulator.new(consumer.to_s)
@@ -82,20 +74,16 @@ module Rigor
82
74
  @mutex.synchronize { @active_count -= 1 }
83
75
  end
84
76
 
85
- # Plain integer read (GVL-atomic) — no `Thread.current` lookup on the
86
- # disabled fast path.
77
+ # Plain integer read (GVL-atomic) — no `Thread.current` lookup on the disabled fast path.
87
78
  def active?
88
79
  @active_count.positive?
89
80
  end
90
81
 
91
- # Records that the current consumer read a declaration / body whose
92
- # definition site is `path_line` (a `"path:line"` String, or nil).
93
- # When `symbol` is given (a `"ClassName#method"` String), the read is
94
- # a method-call edge and is recorded at symbol granularity in
95
- # `symbol_sources` in addition to the coarse `sources` set.
96
- # Without `symbol` the read is a class-ancestry edge (file-granularity)
97
- # and is added to `ancestry_sources` only.
98
- # Self-reads and nil sites are ignored in all cases.
82
+ # Records that the current consumer read a declaration / body whose definition site is `path_line` (a
83
+ # `"path:line"` String, or nil). When `symbol` is given (a `"ClassName#method"` String), the read is a
84
+ # method-call edge and is recorded at symbol granularity in `symbol_sources` in addition to the coarse
85
+ # `sources` set. Without `symbol` the read is a class-ancestry edge (file-granularity) and is added to
86
+ # `ancestry_sources` only. Self-reads and nil sites are ignored in all cases.
99
87
  def read_site(path_line, symbol = nil)
100
88
  accumulator = Thread.current[KEY]
101
89
  return if accumulator.nil? || path_line.nil?
@@ -111,8 +99,8 @@ module Rigor
111
99
  end
112
100
  end
113
101
 
114
- # Records a cross-file lookup of `name` (kind `:method` / `:class` /
115
- # `:const` / …) that resolved to nothing — a negative dependency.
102
+ # Records a cross-file lookup of `name` (kind `:method` / `:class` / `:const` / …) that resolved to
103
+ # nothing — a negative dependency.
116
104
  def read_missing(kind, name)
117
105
  accumulator = Thread.current[KEY]
118
106
  accumulator&.missing&.add("#{kind}:#{name}")
@@ -3,26 +3,19 @@
3
3
  module Rigor
4
4
  module Analysis
5
5
  module DependencySourceInference
6
- # ADR-10 slice 5c — per-run accumulator for the
7
- # `dynamic.dependency-source.boundary-cross` `:info`
6
+ # ADR-10 slice 5c — per-run accumulator for the `dynamic.dependency-source.boundary-cross` `:info`
8
7
  # diagnostic.
9
8
  #
10
- # The diagnostic fires when both an authoritative source
11
- # (RBS today; plugins later) AND a `mode: :full` opt-in
12
- # gem's source catalog resolve the same `(class_name,
13
- # method_name)`. The dispatcher takes the authoritative
14
- # source's answer (per ADR-10's tier order), but records
15
- # the boundary crossing so the user can audit whether RBS
16
- # and the gem source have drifted.
9
+ # The diagnostic fires when both an authoritative source (RBS today; plugins later) AND a `mode: :full`
10
+ # opt-in gem's source catalog resolve the same `(class_name, method_name)`. The dispatcher takes the
11
+ # authoritative source's answer (per ADR-10's tier order), but records the boundary crossing so the user
12
+ # can audit whether RBS and the gem source have drifted.
17
13
  #
18
- # The accumulator deduplicates per `(class_name,
19
- # method_name, gem_name)` so a method called from many
14
+ # The accumulator deduplicates per `(class_name, method_name, gem_name)` so a method called from many
20
15
  # files yields one diagnostic.
21
16
  #
22
- # Used in the same pattern as
23
- # {Rigor::RbsExtended::Reporter}: the dispatcher writes
24
- # events into the per-run instance; {Rigor::Analysis::Runner}
25
- # drains it at end-of-run into a flat
17
+ # Used in the same pattern as {Rigor::RbsExtended::Reporter}: the dispatcher writes events into the
18
+ # per-run instance; {Rigor::Analysis::Runner} drains it at end-of-run into a flat
26
19
  # `Rigor::Analysis::Diagnostic` list.
27
20
  class BoundaryCrossReporter
28
21
  Entry = Data.define(:class_name, :method_name, :gem_name, :rbs_display)
@@ -32,12 +25,9 @@ module Rigor
32
25
  @mutex = Mutex.new
33
26
  end
34
27
 
35
- # @return [Array<Entry>] frozen snapshot of the recorded
36
- # boundary-cross events. Each entry is a Data with
37
- # `class_name` (String), `method_name` (Symbol),
38
- # `gem_name` (String), and `rbs_display` (String —
39
- # the authoritative-side type's human-facing form,
40
- # embedded into the diagnostic message).
28
+ # @return [Array<Entry>] frozen snapshot of the recorded boundary-cross events. Each entry is a Data
29
+ # with `class_name` (String), `method_name` (Symbol), `gem_name` (String), and `rbs_display` (String
30
+ # the authoritative-side type's human-facing form, embedded into the diagnostic message).
41
31
  def entries
42
32
  @mutex.synchronize { @entries.dup.freeze }
43
33
  end
@@ -46,10 +36,8 @@ module Rigor
46
36
  @mutex.synchronize { @entries.empty? }
47
37
  end
48
38
 
49
- # Records one boundary-cross event. Deduplicates on the
50
- # `(class_name, method_name, gem_name)` triple the
51
- # diagnostic per-receiver-per-method-per-owning-gem is
52
- # the actionable unit.
39
+ # Records one boundary-cross event. Deduplicates on the `(class_name, method_name, gem_name)` triple —
40
+ # the diagnostic per-receiver-per-method-per-owning-gem is the actionable unit.
53
41
  def record(class_name:, method_name:, gem_name:, rbs_display:)
54
42
  entry = Entry.new(
55
43
  class_name: class_name, method_name: method_name,
@@ -7,17 +7,13 @@ require_relative "walker"
7
7
  module Rigor
8
8
  module Analysis
9
9
  module DependencySourceInference
10
- # Folds a `Configuration::Dependencies` value into a
11
- # frozen {Index}. Resolves each non-disabled entry through
12
- # {GemResolver}, walks each resolved gem's `roots:` via
13
- # {Walker.walk} under the configured `budget_per_gem` cap,
14
- # and aggregates the per-gem method catalogs into the
15
- # Index's flat `(class_name, method_name) → kind` table.
10
+ # Folds a `Configuration::Dependencies` value into a frozen {Index}. Resolves each non-disabled entry
11
+ # through {GemResolver}, walks each resolved gem's `roots:` via {Walker.walk} under the configured
12
+ # `budget_per_gem` cap, and aggregates the per-gem method catalogs into the Index's flat
13
+ # `(class_name, method_name) kind` table.
16
14
  #
17
- # Entries with `mode: :disabled` are skipped without
18
- # resolution attempts so users can "list and disable" a
19
- # gem in configuration without provoking a missing-gem
20
- # diagnostic.
15
+ # Entries with `mode: :disabled` are skipped without resolution attempts so users can "list and disable"
16
+ # a gem in configuration without provoking a missing-gem diagnostic.
21
17
  module Builder
22
18
  module_function
23
19
 
@@ -36,12 +32,9 @@ module Rigor
36
32
  state.to_index(dependencies)
37
33
  end
38
34
 
39
- # Per-build mutable accumulator. The original inline
40
- # variables (`resolved` / `unresolvable` / `catalog` /
41
- # `class_to_gem` / `budget_exceeded` / `gem_modes`)
42
- # pushed the method past the AbcSize budget; the
43
- # struct collects the same fields, narrows
44
- # `absorb`'s branching, and yields one
35
+ # Per-build mutable accumulator. The original inline variables (`resolved` / `unresolvable` /
36
+ # `catalog` / `class_to_gem` / `budget_exceeded` / `gem_modes`) pushed the method past the AbcSize
37
+ # budget; the struct collects the same fields, narrows `absorb`'s branching, and yields one
45
38
  # `Index.new(...)` call from `to_index`.
46
39
  class BuildState
47
40
  def initialize
@@ -82,26 +75,20 @@ module Rigor
82
75
  end
83
76
  end
84
77
 
85
- # ADR-10 5b — per-class reverse-lookup table (β budget
86
- # semantics). Records `class_name gem_name` for every
87
- # class observed in the gem's catalog. First-write-wins:
88
- # if two opt-in gems re-open the same class, the first
89
- # gem to harvest the class owns it in the reverse index.
90
- # The dispatcher only consults this map when the
91
- # `budget_overrun_strategy` is `:dependency_silence`,
92
- # so the storage cost is never paid back unless the
93
- # user opts in.
78
+ # ADR-10 5b — per-class reverse-lookup table (β budget semantics). Records `class_name → gem_name` for
79
+ # every class observed in the gem's catalog. First-write-wins: if two opt-in gems re-open the same
80
+ # class, the first gem to harvest the class owns it in the reverse index. The dispatcher only consults
81
+ # this map when the `budget_overrun_strategy` is `:dependency_silence`, so the storage cost is never
82
+ # paid back unless the user opts in.
94
83
  def record_class_to_gem(catalog, gem_name, class_to_gem)
95
84
  catalog.each_key do |(class_name, _method_name)|
96
85
  class_to_gem[class_name] ||= gem_name
97
86
  end
98
87
  end
99
88
 
100
- # Per-resolved-gem walk. Isolated so a single gem's
101
- # filesystem error / parse failure cannot abort the
102
- # build; the walker swallows its own per-file errors,
103
- # and a top-level raise here degrades the gem to "no
104
- # contributions" without touching the rest of the run.
89
+ # Per-resolved-gem walk. Isolated so a single gem's filesystem error / parse failure cannot abort the
90
+ # build; the walker swallows its own per-file errors, and a top-level raise here degrades the gem to
91
+ # "no contributions" without touching the rest of the run.
105
92
  def walker_outcome_for(resolved, budget)
106
93
  Walker.walk(gem_dir: resolved.gem_dir, roots: resolved.roots, budget: budget)
107
94
  rescue StandardError
@@ -3,34 +3,26 @@
3
3
  module Rigor
4
4
  module Analysis
5
5
  module DependencySourceInference
6
- # Maps a `Configuration::Dependencies::Entry` to the gem's
7
- # on-disk installation directory by consulting RubyGems
8
- # (`Gem.loaded_specs` first, falling back to
9
- # `Gem::Specification.find_by_name`). Returns either a
10
- # frozen {Resolved} value object or an {Unresolvable} value
11
- # describing why the gem cannot participate in this run.
6
+ # Maps a `Configuration::Dependencies::Entry` to the gem's on-disk installation directory by consulting
7
+ # RubyGems (`Gem.loaded_specs` first, falling back to `Gem::Specification.find_by_name`). Returns either
8
+ # a frozen {Resolved} value object or an {Unresolvable} value describing why the gem cannot participate
9
+ # in this run.
12
10
  #
13
- # Resolution failures are surfaced as
14
- # `dynamic.dependency-source.gem-not-found` diagnostics by
15
- # {Analysis::Runner} rather than crashing the run, so a
16
- # missing gem in `dependencies.source_inference` degrades
17
- # cleanly to "no contributions from that gem" — every other
18
- # gem and the project source remain unaffected.
11
+ # Resolution failures are surfaced as `dynamic.dependency-source.gem-not-found` diagnostics by
12
+ # {Analysis::Runner} rather than crashing the run, so a missing gem in `dependencies.source_inference`
13
+ # degrades cleanly to "no contributions from that gem" — every other gem and the project source remain
14
+ # unaffected.
19
15
  module GemResolver
20
- # Successful resolution. `version` is the spec version as
21
- # a String so it round-trips into cache descriptors
22
- # (slice 3) without leaking a `Gem::Version` instance
23
- # through public surfaces.
16
+ # Successful resolution. `version` is the spec version as a String so it round-trips into cache
17
+ # descriptors (slice 3) without leaking a `Gem::Version` instance through public surfaces.
24
18
  class Resolved < Data.define(:gem_name, :version, :gem_dir, :mode, :roots)
25
19
  def descriptor_key
26
20
  [gem_name, version, mode].freeze
27
21
  end
28
22
  end
29
23
 
30
- # Unresolvable reasons. `:not_in_bundle` covers both the
31
- # "RubyGems doesn't know this gem" case and the
32
- # `LoadError`-style raise from `find_by_name`. Future
33
- # reasons (`:c_extension_only`, `:no_lib_root`) are
24
+ # Unresolvable reasons. `:not_in_bundle` covers both the "RubyGems doesn't know this gem" case and the
25
+ # `LoadError`-style raise from `find_by_name`. Future reasons (`:c_extension_only`, `:no_lib_root`) are
34
26
  # introduced as the walker discovers them in slice 2b.
35
27
  Unresolvable = Data.define(:gem_name, :reason)
36
28
 
@@ -53,12 +45,9 @@ module Rigor
53
45
  )
54
46
  end
55
47
 
56
- # Locator. `Gem.loaded_specs` reflects the bundle (cheap
57
- # lookup, no filesystem walk); `find_by_name` is the
58
- # broader fallback for gems present on the gem path but
59
- # not yet `require`'d. `Gem::MissingSpecError` is a
60
- # `LoadError` subclass, so the rescue covers both
61
- # missing-spec and load-error signals.
48
+ # Locator. `Gem.loaded_specs` reflects the bundle (cheap lookup, no filesystem walk); `find_by_name` is
49
+ # the broader fallback for gems present on the gem path but not yet `require`'d. `Gem::MissingSpecError`
50
+ # is a `LoadError` subclass, so the rescue covers both missing-spec and load-error signals.
62
51
  def locate_gem_spec(name)
63
52
  Gem.loaded_specs[name] || begin
64
53
  Gem::Specification.find_by_name(name)
@@ -5,45 +5,31 @@ require_relative "../../cache/descriptor"
5
5
  module Rigor
6
6
  module Analysis
7
7
  module DependencySourceInference
8
- # Per-run collection of gem-source-inference state. Holds
9
- # the resolved gems the walker visits plus the unresolvable
10
- # entries the runner surfaces as
11
- # `dynamic.dependency-source.gem-not-found` diagnostics.
12
- # The method table is fully populated by {Walker} at build
13
- # time; {#contribution_for} returns live entries.
8
+ # Per-run collection of gem-source-inference state. Holds the resolved gems the walker visits plus the
9
+ # unresolvable entries the runner surfaces as `dynamic.dependency-source.gem-not-found` diagnostics.
10
+ # The method table is fully populated by {Walker} at build time; {#contribution_for} returns live
11
+ # entries.
14
12
  class Index
15
13
  attr_reader :resolved_gems, :unresolvable, :method_catalog, :budget_exceeded,
16
14
  :class_to_gem, :budget_overrun_strategy, :gem_modes
17
15
 
18
- # @param method_catalog [Hash{[String, Symbol] => Symbol}]
19
- # the flat `(class_name, method_name) → :instance | :singleton`
20
- # table produced by {Walker.walk}, aggregated across
21
- # every resolved gem in the run. The Index itself stays
22
- # gem-agnostic the per-gem attribution that slice 3's
23
- # cache descriptor needs lives on `Resolved`, not here.
24
- # @param budget_exceeded [Array<String>] gem names whose
25
- # {Walker} run hit the per-gem catalog cap (slice 4).
26
- # The Runner consumes this list to emit one
27
- # `dynamic.dependency-source.budget-exceeded` warning
28
- # per gem.
29
- # @param class_to_gem [Hash<String, String>] reverse
30
- # lookup `class_namegem_name` (slice 5b). Built
31
- # first-write-wins: when two opt-in gems re-open the
32
- # same class, the first gem owns it. The dispatcher
33
- # consults this map under the `:dependency_silence`
34
- # budget overrun strategy so call sites on a
35
- # budget-exceeded gem's classes degrade to
36
- # `Dynamic[top]` instead of falling through to the
37
- # user-class fallback.
38
- # @param gem_modes [Hash<String, Symbol>] per-gem mode
39
- # table (`gem_name → :disabled | :when_missing |
40
- # :full`). ADR-10 slice 5c consults this through
41
- # {#mode_for} to identify call sites where gem-source
42
- # and RBS both contribute under `mode: :full`. The map
43
- # is keyed on `gem_name` (not class) because re-opened
44
- # classes belong to the first gem they appeared in
45
- # per `class_to_gem`; `mode_for(class_name)` chains
46
- # the two lookups.
16
+ # @param method_catalog [Hash{[String, Symbol] => Symbol}] the flat
17
+ # `(class_name, method_name) → :instance | :singleton` table produced by {Walker.walk}, aggregated
18
+ # across every resolved gem in the run. The Index itself stays gem-agnostic — the per-gem
19
+ # attribution that slice 3's cache descriptor needs lives on `Resolved`, not here.
20
+ # @param budget_exceeded [Array<String>] gem names whose {Walker} run hit the per-gem catalog cap
21
+ # (slice 4). The Runner consumes this list to emit one `dynamic.dependency-source.budget-exceeded`
22
+ # warning per gem.
23
+ # @param class_to_gem [Hash<String, String>] reverse lookup `class_name → gem_name` (slice 5b). Built
24
+ # first-write-wins: when two opt-in gems re-open the same class, the first gem owns it. The
25
+ # dispatcher consults this map under the `:dependency_silence` budget overrun strategy so call
26
+ # sites on a budget-exceeded gem's classes degrade to `Dynamic[top]` instead of falling through to
27
+ # the user-class fallback.
28
+ # @param gem_modes [Hash<String, Symbol>] per-gem mode table (`gem_name:disabled | :when_missing |
29
+ # :full`). ADR-10 slice 5c consults this through {#mode_for} to identify call sites where
30
+ # gem-source and RBS both contribute under `mode: :full`. The map is keyed on `gem_name` (not
31
+ # class) because re-opened classes belong to the first gem they appeared in per `class_to_gem`;
32
+ # `mode_for(class_name)` chains the two lookups.
47
33
  def initialize(
48
34
  resolved_gems: [], unresolvable: [], method_catalog: {},
49
35
  budget_exceeded: [], class_to_gem: {},
@@ -59,12 +45,10 @@ module Rigor
59
45
  freeze
60
46
  end
61
47
 
62
- # Accepts both the post-heuristic `CatalogEntry` shape
63
- # (the Walker's current output) and the legacy bare-Symbol
64
- # `:instance` / `:singleton` shape (older tests + earlier
65
- # in-tree callers). Symbol values are wrapped into
66
- # `CatalogEntry(kind: value, return_type: nil)` so the
67
- # downstream dispatcher always sees a single shape.
48
+ # Accepts both the post-heuristic `CatalogEntry` shape (the Walker's current output) and the legacy
49
+ # bare-Symbol `:instance` / `:singleton` shape (older tests + earlier in-tree callers). Symbol values
50
+ # are wrapped into `CatalogEntry(kind: value, return_type: nil)` so the downstream dispatcher always
51
+ # sees a single shape.
68
52
  def normalize_catalog(catalog)
69
53
  catalog.transform_values do |value|
70
54
  value.is_a?(Symbol) ? Walker::CatalogEntry.new(kind: value) : value
@@ -72,16 +56,14 @@ module Rigor
72
56
  end
73
57
  private :normalize_catalog
74
58
 
75
- # @return [String, nil] the gem that owns `class_name`
76
- # (first-write-wins); `nil` when the class isn't in
77
- # any opt-in gem's catalog.
59
+ # @return [String, nil] the gem that owns `class_name` (first-write-wins); `nil` when the class isn't
60
+ # in any opt-in gem's catalog.
78
61
  def gem_for(class_name)
79
62
  @class_to_gem[class_name]
80
63
  end
81
64
 
82
- # ADR-10 slice 5c — per-class mode lookup. Chains
83
- # `class_to_gem` + `gem_modes`; returns `nil` when the
84
- # class isn't owned by any opt-in gem in this run.
65
+ # ADR-10 slice 5c — per-class mode lookup. Chains `class_to_gem` + `gem_modes`; returns `nil` when
66
+ # the class isn't owned by any opt-in gem in this run.
85
67
  def mode_for(class_name)
86
68
  gem_name = @class_to_gem[class_name]
87
69
  return nil if gem_name.nil?
@@ -89,22 +71,17 @@ module Rigor
89
71
  @gem_modes[gem_name]
90
72
  end
91
73
 
92
- # ADR-10 slice 5c — true when the receiver class belongs
93
- # to a gem the user opted into `mode: :full` for. The
94
- # dispatcher consults this AFTER an authoritative-source
95
- # (RBS / plugin) dispatch resolves so it can record the
96
- # boundary-crossing for audit.
74
+ # ADR-10 slice 5c — true when the receiver class belongs to a gem the user opted into `mode: :full`
75
+ # for. The dispatcher consults this AFTER an authoritative-source (RBS / plugin) dispatch resolves
76
+ # so it can record the boundary-crossing for audit.
97
77
  def full_mode?(class_name)
98
78
  mode_for(class_name) == :full
99
79
  end
100
80
 
101
- # Looks up the recorded method kind for a
102
- # `(class_name, method_name)` pair. Returns `:instance`
103
- # / `:singleton` when the walker observed a definition
104
- # under one of the resolved gems' `roots:`, or `nil`
105
- # otherwise. Slice 2b-ii enriches this with the inferred
106
- # return type so the dispatcher tier can build a
107
- # `Type::Dynamic` directly from the lookup result.
81
+ # Looks up the recorded method kind for a `(class_name, method_name)` pair. Returns `:instance` /
82
+ # `:singleton` when the walker observed a definition under one of the resolved gems' `roots:`, or
83
+ # `nil` otherwise. Slice 2b-ii enriches this with the inferred return type so the dispatcher tier can
84
+ # build a `Type::Dynamic` directly from the lookup result.
108
85
  def contribution_for(class_name:, method_name:)
109
86
  @method_catalog[[class_name, method_name]]
110
87
  end
@@ -113,20 +90,14 @@ module Rigor
113
90
  @resolved_gems.empty?
114
91
  end
115
92
 
116
- # Builds a frozen `Cache::Descriptor` carrying one
117
- # `DependencyEntry` row per resolved gem in this run.
118
- # Cache producers that observe ADR-10 inference outputs
119
- # compose this descriptor with their own (RBS, plugin,
120
- # file-digest) descriptors so a `bundle update` on a
121
- # listed gem invalidates exactly that gem's slice while
122
- # leaving the rest of the cache hot.
93
+ # Builds a frozen `Cache::Descriptor` carrying one `DependencyEntry` row per resolved gem in this
94
+ # run. Cache producers that observe ADR-10 inference outputs compose this descriptor with their own
95
+ # (RBS, plugin, file-digest) descriptors so a `bundle update` on a listed gem invalidates exactly
96
+ # that gem's slice while leaving the rest of the cache hot.
123
97
  #
124
- # Unresolvable entries contribute nothing — there is no
125
- # version to key on, and the runner already surfaces them
126
- # as `dynamic.dependency-source.gem-not-found`
127
- # diagnostics. Resolved-but-disabled entries are also
128
- # absent: the {Builder} skips them before resolution, so
129
- # they never reach the index.
98
+ # Unresolvable entries contribute nothing — there is no version to key on, and the runner already
99
+ # surfaces them as `dynamic.dependency-source.gem-not-found` diagnostics. Resolved-but-disabled
100
+ # entries are also absent: the {Builder} skips them before resolution, so they never reach the index.
130
101
  def cache_descriptor
131
102
  dependencies = @resolved_gems.map do |resolved|
132
103
  Cache::Descriptor::DependencyEntry.new(
@@ -139,10 +110,8 @@ module Rigor
139
110
  end
140
111
  end
141
112
 
142
- # Frozen empty index — the runner uses this when
143
- # `Configuration#dependencies.source_inference` is empty
144
- # so the dispatcher tier holds a stable, non-nil
145
- # reference even on default configurations.
113
+ # Frozen empty index — the runner uses this when `Configuration#dependencies.source_inference` is
114
+ # empty so the dispatcher tier holds a stable, non-nil reference even on default configurations.
146
115
  Index::EMPTY = Index.new.freeze
147
116
  end
148
117
  end
@@ -7,42 +7,30 @@ require_relative "../../type"
7
7
  module Rigor
8
8
  module Analysis
9
9
  module DependencySourceInference
10
- # Walker enhancement (ADR-10 § "Open questions"): pulls a
11
- # **heuristic** return type out of a method body's tail
12
- # expression. The heuristic is intentionally narrow only
13
- # the trivially-decidable shapes contribute. Everything
14
- # else returns `nil`, which the dispatcher consumes as
15
- # "fall back to `Dynamic[top]`" (the pre-enhancement
16
- # behaviour).
10
+ # Walker enhancement (ADR-10 § "Open questions"): pulls a **heuristic** return type out of a method
11
+ # body's tail expression. The heuristic is intentionally narrow only the trivially-decidable shapes
12
+ # contribute. Everything else returns `nil`, which the dispatcher consumes as "fall back to
13
+ # `Dynamic[top]`" (the pre-enhancement behaviour).
17
14
  #
18
15
  # The contract is a strict floor:
19
16
  #
20
- # - Last statement is a literal scalar (Integer / Float /
21
- # Symbol / true / false / nil) → `Constant<value>`.
22
- # - Last statement is a String literal `Nominal[String]`
23
- # (NOT `Constant<"x">`; a String literal is mutable under
24
- # `# frozen_string_literal: false`, so the analyzer cannot
25
- # claim object identity).
26
- # - Last statement is an Array / Hash literal →
27
- # `Nominal[Array]` / `Nominal[Hash]` (element-type
28
- # inference stays deferred — too expensive for the
29
- # heuristic tier).
30
- # - Last statement is `self` → `nil` (the caller's receiver
31
- # nominal would be more accurate but the walker doesn't
32
- # carry the receiver context; deferred).
17
+ # - Last statement is a literal scalar (Integer / Float / Symbol / true / false / nil) → `Constant<value>`.
18
+ # - Last statement is a String literal → `Nominal[String]` (NOT `Constant<"x">`; a String literal is
19
+ # mutable under `# frozen_string_literal: false`, so the analyzer cannot claim object identity).
20
+ # - Last statement is an Array / Hash literal `Nominal[Array]` / `Nominal[Hash]` (element-type
21
+ # inference stays deferred too expensive for the heuristic tier).
22
+ # - Last statement is `self` → `nil` (the caller's receiver nominal would be more accurate but the
23
+ # walker doesn't carry the receiver context; deferred).
33
24
  # - Anything else → `nil`.
34
25
  #
35
- # The dispatcher wraps the returned type in `Dynamic[T]`
36
- # before returning to the user per ADR-10's `Dynamic`-origin
37
- # contract. The wrapping is the dispatcher's responsibility,
38
- # not the heuristic's.
26
+ # The dispatcher wraps the returned type in `Dynamic[T]` before returning to the user per ADR-10's
27
+ # `Dynamic`-origin contract. The wrapping is the dispatcher's responsibility, not the heuristic's.
39
28
  module ReturnTypeHeuristic
40
29
  module_function
41
30
 
42
31
  # @param def_node [Prism::DefNode]
43
- # @return [Rigor::Type, nil] heuristic return type, or
44
- # `nil` when the body's tail expression doesn't match
45
- # any of the recognised shapes.
32
+ # @return [Rigor::Type, nil] heuristic return type, or `nil` when the body's tail expression doesn't
33
+ # match any of the recognised shapes.
46
34
  def extract(def_node)
47
35
  body = def_node.body
48
36
  return nil if body.nil?
@@ -51,11 +39,9 @@ module Rigor
51
39
  literal_return_type(tail)
52
40
  end
53
41
 
54
- # Extracts the last evaluated expression of the method
55
- # body. A `Prism::DefNode`'s body is either a
56
- # `StatementsNode` (multi-statement body) or a single
57
- # expression node directly. We dig past `BeginNode` /
58
- # rescue wrappers to the protected body's tail.
42
+ # Extracts the last evaluated expression of the method body. A `Prism::DefNode`'s body is either a
43
+ # `StatementsNode` (multi-statement body) or a single expression node directly. We dig past
44
+ # `BeginNode` / rescue wrappers to the protected body's tail.
59
45
  def tail_expression(node)
60
46
  case node
61
47
  when Prism::StatementsNode
@@ -70,11 +56,9 @@ module Rigor
70
56
  end
71
57
  private_class_method :tail_expression
72
58
 
73
- # The per-shape heuristic. Per the module docstring,
74
- # immutable scalar literals fold to `Constant<value>`;
75
- # mutable container literals (String, Array, Hash) fold
76
- # to the appropriate Nominal; everything else returns
77
- # nil.
59
+ # The per-shape heuristic. Per the module docstring, immutable scalar literals fold to `Constant<value>`;
60
+ # mutable container literals (String, Array, Hash) fold to the appropriate Nominal; everything else
61
+ # returns nil.
78
62
  def literal_return_type(node)
79
63
  case node
80
64
  when Prism::IntegerNode, Prism::FloatNode then Type::Combinator.constant_of(node.value)
@@ -89,9 +73,8 @@ module Rigor
89
73
  end
90
74
  private_class_method :literal_return_type
91
75
 
92
- # `Prism::SymbolNode#value` returns the Symbol's name as
93
- # a String. We `.to_sym` it for the Constant carrier so
94
- # `:foo` is `Constant<:foo>`, not `Constant<"foo">`.
76
+ # `Prism::SymbolNode#value` returns the Symbol's name as a String. We `.to_sym` it for the Constant
77
+ # carrier so `:foo` is `Constant<:foo>`, not `Constant<"foo">`.
95
78
  def symbol_constant(node)
96
79
  value = node.value
97
80
  return nil if value.nil?