rigortype 0.4.6 → 0.4.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 (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../../source/node_walker"
6
+
7
+ module Rigor
8
+ module Inference
9
+ module ScopeIndexer
10
+ # Issue #2291 — the class-ivar census types an `@x = LIT` write from the type the constant's own write binds, the
11
+ # same way a method body reads `LIT`. The census runs before the file's in-source constant table is seeded, so
12
+ # without it the write typed under no constant table and a reader over the ivar (`def q = @q`) read `Dynamic[top]`
13
+ # where `def g = LIT` read the constant's type.
14
+ #
15
+ # One instance serves one census walk. The file's constant table is built on the first constant-valued write, so
16
+ # a file with none pays nothing; it is built as {ScopeIndexer#index} builds it later (typed under the census's
17
+ # scope, merged over the scope's project seed, a mutated literal widened), and resolved through the ordinary
18
+ # constant lookup. {#type_of} answers nil, leaving the write's ordinary typing, when:
19
+ #
20
+ # - the value is not a constant read (`LIT`, `A::LIT`, `::LIT`); a path over anything else (`self.class::LIT`)
21
+ # resolves to nothing, as it does in a method body;
22
+ # - the file may rebind a constant of that last segment: a second write in any form, an operator, `||=` or `&&=`
23
+ # write, a multiple-assignment target, or a `const_set` / `remove_const` naming it — or naming anything but a
24
+ # literal, which may be it.
25
+ #
26
+ # A constant another file assigns resolves only through the scope's own project seed, as it did before.
27
+ class ConstantIvarValue
28
+ WRITE_NODES = [Prism::ConstantWriteNode, Prism::ConstantPathWriteNode].freeze
29
+ REBIND_NODES = [Prism::ConstantOperatorWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantAndWriteNode,
30
+ Prism::ConstantPathOperatorWriteNode, Prism::ConstantPathOrWriteNode,
31
+ Prism::ConstantPathAndWriteNode, Prism::ConstantTargetNode,
32
+ Prism::ConstantPathTargetNode].freeze
33
+ REBIND_CALLS = %i[const_set remove_const].freeze
34
+ ANY_NAME = :any
35
+ private_constant :WRITE_NODES, :REBIND_NODES, :REBIND_CALLS, :ANY_NAME
36
+
37
+ def initialize(root, scope)
38
+ @root = root
39
+ @scope = scope
40
+ end
41
+
42
+ # The type `value` reads under the file's constant table, or nil when the write keeps its ordinary typing.
43
+ def type_of(value, scope)
44
+ return nil unless value.is_a?(Prism::ConstantReadNode) || value.is_a?(Prism::ConstantPathNode)
45
+ return nil if rebound?(value.name)
46
+
47
+ scope.with_discovery(scope.discovery.with(in_source_constants: constants)).type_of(value)
48
+ end
49
+
50
+ private
51
+
52
+ def constants
53
+ @constants ||= begin
54
+ literal_mutations = ScopeIndexer.collect_literal_receiver_mutations(@root)
55
+ per_file = ScopeIndexer.widen_mutated_constants(
56
+ ScopeIndexer.build_in_source_constants(@root, @scope), literal_mutations[:constants]
57
+ )
58
+ ScopeIndexer.merge_seeded_constants(@scope.in_source_constants, per_file)
59
+ end
60
+ end
61
+
62
+ def rebound?(name)
63
+ @rebound ||= rebound_names
64
+ @rebound.include?(ANY_NAME) || @rebound.include?(name)
65
+ end
66
+
67
+ def rebound_names
68
+ written = Set.new
69
+ rebound = Set.new
70
+ Source::NodeWalker.each(@root) do |node|
71
+ case node
72
+ when *WRITE_NODES
73
+ name = write_name(node)
74
+ rebound << name unless written.add?(name)
75
+ when *REBIND_NODES then rebound << write_name(node)
76
+ when Prism::CallNode then rebound << call_name(node) if REBIND_CALLS.include?(node.name)
77
+ end
78
+ end
79
+ rebound
80
+ end
81
+
82
+ def write_name(node) = node.respond_to?(:target) ? node.target.name : node.name
83
+
84
+ def call_name(node)
85
+ argument = node.arguments&.arguments&.first
86
+ return argument.unescaped.to_sym if argument.is_a?(Prism::SymbolNode) || argument.is_a?(Prism::StringNode)
87
+
88
+ ANY_NAME
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -4,8 +4,10 @@ require "prism"
4
4
 
5
5
  require_relative "../../reflection"
6
6
  require_relative "../method_parameter_binder"
7
+ require_relative "../../typing_state"
7
8
  require_relative "../global_write_census"
8
9
  require_relative "unseen_ivar_writers"
10
+ require_relative "derived_param_value"
9
11
 
10
12
  module Rigor
11
13
  module Inference
@@ -21,7 +23,13 @@ module Rigor
21
23
  # every gate, and {EMPTY} otherwise; an absent write keeps the census's ordinary rvalue typing, so a declined
22
24
  # case reads exactly what it read before:
23
25
  #
24
- # - the write's value is a bare read of the parameter (`@x = x`), not an expression over it;
26
+ # - the write's value reads at least one parameter, and every local it reads is a parameter with a declared type.
27
+ # A bare read (`@x = x`) records that type; an expression over parameters (`@x = x.dup.freeze`,
28
+ # `@x = Array(x)`, `@x = f ? x : y`, issue #2321) records the value typed with each parameter it reads bound to
29
+ # its declared type, unless {DerivedParamValue} declines it (a parameter that holds `untyped` at any depth or
30
+ # has a default outside its declared type, or a call argument whose type may match a narrower overload). A
31
+ # value that holds a block, a lambda or a nested `def` is not credited: their own parameters are locals the
32
+ # scan does not bind;
25
33
  # - the write is not inside a block or a lambda, so a block parameter of the same name and a block that runs
26
34
  # under another `self` (`instance_eval`, `Class.new`, `define_method`) never borrow the method's parameter;
27
35
  # - the method body never writes the parameter, in any form and at any depth (`x = Integer(x)`, `x ||= …`,
@@ -33,8 +41,8 @@ module Rigor
33
41
  # - the class's primary RBS declaration is the project's (`sig/` or rbs-inline), not core, stdlib or a gem's:
34
42
  # a project reopening of a library class (`Thread::SizedQueue#initialize`) would otherwise borrow the
35
43
  # library's parameter type for its own `def`;
36
- # - the body neither calls `binding` nor passes code to `eval` / `instance_eval` / `class_eval` /
37
- # `module_eval` / `instance_exec` as an argument, any of which can rewrite the parameter unseen;
44
+ # - the body neither calls `binding` nor passes code as an argument to an `eval`-family call
45
+ # (`OPAQUE_EVAL_CALLS`), any of which can rewrite the parameter unseen;
38
46
  # - nothing outside the class's own census may write the ivar ({UnseenIvarWriters}): no project ancestor, and
39
47
  # no other object's code (`o.instance_variable_set(:@x, v)`);
40
48
  # - the declared type is not `untyped`, which is what the write already records.
@@ -51,27 +59,27 @@ module Rigor
51
59
  module_exec].to_set.freeze
52
60
  private_constant :OPAQUE_EVAL_CALLS
53
61
 
54
- WRITER_SCOPE_KEY = :__rigor_declared_param_writer_scope__
55
- private_constant :WRITER_SCOPE_KEY
62
+ # ADR-129 WD1 — a Frame: the scope is installed around a census and restored on the way out.
63
+ WRITER_SCOPE = TypingState.frame(
64
+ :declared_param_writer_scope,
65
+ reason: "the scope whose project tables the writer gates read, installed per census",
66
+ # G1′: the census types its defaults under the Frame, but only the writer gates, outside `type_of`, read it.
67
+ typing_never_reads: true
68
+ )
69
+ private_constant :WRITER_SCOPE
56
70
 
57
71
  module_function
58
72
 
59
73
  # Runs the block with `scope` as the one whose project tables the unseen-writer and single-file gates read, in
60
- # place of the census's own scope. `rigor sig-gen` indexes each file under a scope that carries no project
61
- # tables, so without it another file's writer, ancestor or reopening would go unseen.
62
- def with_writer_scope(scope)
74
+ # place of the census's own scope. `rigor sig-gen` indexes each file over the project tables less the member
75
+ # layouts; this scope carries all of them, so another file's writer, ancestor or reopening is seen.
76
+ def with_writer_scope(scope, &)
63
77
  return yield if scope.nil?
64
78
 
65
- previous = Thread.current[WRITER_SCOPE_KEY]
66
- Thread.current[WRITER_SCOPE_KEY] = scope
67
- begin
68
- yield
69
- ensure
70
- Thread.current[WRITER_SCOPE_KEY] = previous
71
- end
79
+ TypingState.current.with(WRITER_SCOPE, scope, &)
72
80
  end
73
81
 
74
- def writer_scope(scope) = Thread.current[WRITER_SCOPE_KEY] || scope
82
+ def writer_scope(scope) = TypingState.current[WRITER_SCOPE] || scope
75
83
 
76
84
  # `{InstanceVariableWriteNode => Rigor::Type}` for `def_node`'s creditable writes, or {EMPTY}. The caller
77
85
  # passes only an instance `def`: a singleton body's `@x` is the class object's.
@@ -82,13 +90,15 @@ module Rigor
82
90
  writes = []
83
91
  return EMPTY if scan(def_node.body, written, writes, in_block: false) == :opaque
84
92
 
85
- writes.reject! { |write| written.include?(write.value.name) }
86
- return EMPTY if writes.empty?
93
+ reads = unwritten_reads(writes, written)
94
+ return EMPTY if reads.empty?
87
95
 
88
96
  types = declared_types(def_node, class_name, scope)
89
97
  writers = writer_scope(scope)
90
- credited = writes.each_with_object({}.compare_by_identity) do |write, acc|
91
- type = types[write.value.name]
98
+ credited = reads.each_with_object({}.compare_by_identity) do |(write, names), acc|
99
+ next unless names.all? { |name| types.key?(name) }
100
+
101
+ type = value_type(write.value, names, types, def_node, scope)
92
102
  acc[write] = type if type && !UnseenIvarWriters.possible?(writers, class_name, write.name)
93
103
  end
94
104
  credited.empty? ? EMPTY : credited
@@ -96,7 +106,45 @@ module Rigor
96
106
  EMPTY
97
107
  end
98
108
 
99
- # Collects the `@x = local` writes outside every block, and the name of every local the body writes in
109
+ # `{write => names of the locals its value reads}` for each write whose value reads at least one local and no
110
+ # local the body writes.
111
+ def unwritten_reads(writes, written)
112
+ writes.each_with_object({}.compare_by_identity) do |write, acc|
113
+ names = parameter_reads(write.value)
114
+ next if names.nil? || names.empty? || names.any? { |name| written.include?(name) }
115
+
116
+ acc[write] = names
117
+ end
118
+ end
119
+
120
+ # The names of the locals `value` reads, or nil when it holds a block, a lambda or a scope barrier.
121
+ def parameter_reads(value, names = Set.new)
122
+ return names unless value.is_a?(Prism::Node)
123
+ return nil if BLOCK_NODES.any? { |klass| value.is_a?(klass) }
124
+ return nil if SCOPE_BARRIERS.any? { |klass| value.is_a?(klass) }
125
+
126
+ names << value.name if value.is_a?(Prism::LocalVariableReadNode)
127
+ value.rigor_each_child do |child|
128
+ return nil if parameter_reads(child, names).nil?
129
+ end
130
+ names
131
+ end
132
+
133
+ # A bare read takes the declared type itself; an expression over the parameters is typed with each bound to
134
+ # its declared type, unless {DerivedParamValue} declines it.
135
+ def value_type(value, names, types, def_node, scope)
136
+ return types[value.name] if value.is_a?(Prism::LocalVariableReadNode)
137
+ return nil if DerivedParamValue.declined?(names, types, def_node, scope)
138
+
139
+ bound = names.reduce(scope) { |acc, name| acc.with_local(name, types[name]) }
140
+ return nil if DerivedParamValue.overload_sensitive_argument?(value, bound)
141
+
142
+ bound.type_of(value)
143
+ rescue StandardError
144
+ nil
145
+ end
146
+
147
+ # Collects the `@x = …` writes outside every block, and the name of every local the body writes in
100
148
  # any form at any depth. A read at depth zero outside a block names the method's own local, which is the
101
149
  # parameter when the body never writes the name.
102
150
  #
@@ -113,7 +161,7 @@ module Rigor
113
161
  when Prism::CallNode
114
162
  return :opaque if opaque_call?(node)
115
163
  end
116
- writes << node if !in_block && local_read_write?(node)
164
+ writes << node if !in_block && node.is_a?(Prism::InstanceVariableWriteNode)
117
165
  inner = in_block || BLOCK_NODES.any? { |klass| node.is_a?(klass) }
118
166
  node.rigor_each_child do |child|
119
167
  return :opaque if scan(child, written, writes, in_block: inner) == :opaque
@@ -129,10 +177,6 @@ module Rigor
129
177
  OPAQUE_EVAL_CALLS.include?(node.name) && !(node.arguments&.arguments || []).empty?
130
178
  end
131
179
 
132
- def local_read_write?(node)
133
- node.is_a?(Prism::InstanceVariableWriteNode) && node.value.is_a?(Prism::LocalVariableReadNode)
134
- end
135
-
136
180
  # The declared, non-`untyped` type of each parameter, or `{}` when a gate declines the method.
137
181
  def declared_types(def_node, class_name, scope)
138
182
  environment = scope&.environment
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../acceptance"
6
+
7
+ module Rigor
8
+ module Inference
9
+ module ScopeIndexer
10
+ # Issue #2321 — the declines {DeclaredParamWrites} applies to a write whose value is an expression over declared
11
+ # parameters, beyond the ones a bare read (`@x = x`) shares. Each says the expression's type, evaluated with the
12
+ # parameters bound to their declared types, may not be the type the ivar holds at runtime:
13
+ #
14
+ # - a parameter whose declared type holds `untyped` at any depth (`Array[untyped]`, #2325): the answer is built
15
+ # on the part the author left untyped;
16
+ # - an optional parameter whose default value's type is not within its declared type
17
+ # (`def initialize(x = nil)` under `(?Integer x)`): the value may run on the default, which the binding does not
18
+ # carry;
19
+ # - an argument of any call in the value (`a + b`, `a + (b > 0 ? b : 0.5)`, `a[i]`, `Array(x)`, `super(…)`,
20
+ # `yield(…)`), typed under the parameter bindings, whose type is anything but a literal or an instance of a
21
+ # class no RBS class names as its superclass: the engine picks the first overload such an argument matches
22
+ # (`Integer#+` given `Integer | Float` answers `Integer`), not the join of every overload it may reach. A
23
+ # receiver dispatches per member and is not declined. A splat or keyword argument types as neither, so it
24
+ # declines too.
25
+ module DerivedParamValue
26
+ module_function
27
+
28
+ # Whether a write over the parameters `names`, declared `types`, is not credited for a parameter it reads.
29
+ def declined?(names, types, def_node, scope)
30
+ names.any? do |name|
31
+ carries_dynamic?(types[name]) || default_outside?(def_node, name, types[name], scope)
32
+ end
33
+ end
34
+
35
+ # Whether a call in `value` takes an argument whose type, under the parameter bindings of `bound`, may match an
36
+ # overload narrower than itself.
37
+ def overload_sensitive_argument?(value, bound)
38
+ return false unless value.is_a?(Prism::Node)
39
+
40
+ if value.is_a?(Prism::CallNode) || value.is_a?(Prism::SuperNode) || value.is_a?(Prism::YieldNode)
41
+ arguments = value.arguments&.arguments || []
42
+ return true if arguments.any? { |argument| overload_sensitive?(bound.type_of(argument), bound) }
43
+ end
44
+ value.rigor_each_child { |child| return true if overload_sensitive_argument?(child, bound) }
45
+ false
46
+ end
47
+
48
+ def carries_dynamic?(type)
49
+ case type
50
+ when Type::Dynamic then true
51
+ when Type::Union, Type::Intersection then type.members.any? { |member| carries_dynamic?(member) }
52
+ when Type::Nominal then type.type_args.any? { |arg| carries_dynamic?(arg) }
53
+ when Type::Tuple then type.elements.any? { |element| carries_dynamic?(element) }
54
+ when Type::HashShape then type.pairs.each_value.any? { |pair_value| carries_dynamic?(pair_value) }
55
+ # No RBS parameter spelling yields a refinement over `untyped`; the arm keeps the walk total.
56
+ when Type::Difference, Type::Refined then carries_dynamic?(type.base)
57
+ else false
58
+ end
59
+ end
60
+
61
+ # Whether an argument of this declared type may match an overload narrower than itself.
62
+ def overload_sensitive?(type, scope)
63
+ case type
64
+ when Type::Constant then false
65
+ when Type::Nominal then !leaf_class?(type.class_name, scope&.environment)
66
+ else true
67
+ end
68
+ end
69
+
70
+ def leaf_class?(class_name, environment)
71
+ return false if environment.nil? || environment.rbs_module?(class_name)
72
+ # A guard: a validated RBS signature names no unknown class, so this answers only for a broken environment.
73
+ return false unless environment.class_known?(class_name)
74
+
75
+ superclasses = environment.rbs_loader&.superclass_names
76
+ return false if superclasses.nil? || superclasses.empty?
77
+
78
+ !superclasses.include?(class_name.delete_prefix("::"))
79
+ end
80
+
81
+ # Whether `name` is an optional parameter of `def_node` whose default value's type is not within `declared`.
82
+ def default_outside?(def_node, name, declared, scope)
83
+ default = default_value(def_node.parameters, name)
84
+ return false if default.nil?
85
+
86
+ default_type = scope.type_of(default)
87
+ return true if carries_dynamic?(default_type)
88
+
89
+ !Acceptance.accepts(declared, default_type).yes? && !empty_collection_within?(declared, default_type)
90
+ end
91
+
92
+ # An empty `[]` or `{}` default is within any `Array[T]` or `Hash[K, V]` the declaration names, which the
93
+ # acceptance check answers only `maybe` (the empty literal has no element type to compare).
94
+ def empty_collection_within?(declared, default_type)
95
+ collection =
96
+ if default_type.is_a?(Type::Tuple) && default_type.elements.empty? then "Array"
97
+ elsif default_type.is_a?(Type::HashShape) && default_type.pairs.empty? then "Hash"
98
+ end
99
+ return false if collection.nil?
100
+
101
+ members = declared.is_a?(Type::Union) ? declared.members : [declared]
102
+ members.any? { |member| member.is_a?(Type::Nominal) && member.class_name == collection }
103
+ end
104
+
105
+ def default_value(parameters, name)
106
+ candidates = parameters.optionals + parameters.keywords
107
+ candidates.find { |param| param.respond_to?(:value) && param.name == name }&.value
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require_relative "../../type"
5
+
6
+ module Rigor
7
+ module Inference
8
+ module ScopeIndexer
9
+ # Issue #2499 — the accumulator idiom: a heap slot (an instance, class or global variable, or a constant)
10
+ # seeded with an empty literal and filled later, often from a method, a subclass, a mixin or another file
11
+ # the census cannot see. The seed is never proof that the slot is still empty, so the census records it
12
+ # without its emptiness: `""` and `+""` as `String`, `[]` as `Array[untyped]`, `{}` as
13
+ # `Hash[untyped, untyped]`. `String.new`, `Array.new` and `Hash.new` already type as their nominal.
14
+ #
15
+ # Only the empty spellings count. A non-empty literal (`@order = "asc"`, `MODES = %w[a b]`,
16
+ # `@opts = { a: 1 }`) keeps its exact type: such slots are modes and config tables, and an RBS parameter
17
+ # typed `"asc" | "desc"` must still accept them. A local keeps its flow precision; this applies only where
18
+ # the census seeds a heap slot.
19
+ module EmptySeed
20
+ module_function
21
+
22
+ # `type` with its emptiness dropped when `node` is an empty seed literal, otherwise `type` itself.
23
+ def erase(node, type)
24
+ return type unless empty_seed?(node)
25
+
26
+ members = type.is_a?(Type::Union) ? type.members : [type]
27
+ Type::Combinator.union(*members.map { |member| unpinned(member) })
28
+ end
29
+
30
+ # `{ name => value node }` for each leading target of a multiple assignment whose right-hand side is a literal
31
+ # list (`@a, @b = {}, []`), so each slot is erased from its own element. Empty for any other right-hand side.
32
+ def multi_write_values(node)
33
+ value = node.value
34
+ return EMPTY_VALUES unless value.is_a?(Prism::ArrayNode)
35
+
36
+ elements = value.elements
37
+ node.lefts.each_with_index.with_object({}) do |(target, index), values|
38
+ break values if index >= elements.size || elements[index].is_a?(Prism::SplatNode)
39
+ next unless target.respond_to?(:name)
40
+
41
+ values[target.name] = elements[index]
42
+ end
43
+ end
44
+
45
+ EMPTY_VALUES = {}.freeze
46
+ private_constant :EMPTY_VALUES
47
+
48
+ # A chained write (`@a = @b = ""`) stores the inner write's value in the outer slot too, so it is followed.
49
+ def empty_seed?(node)
50
+ case node
51
+ when Prism::StringNode then node.unescaped.empty?
52
+ when Prism::ArrayNode, Prism::HashNode then node.elements.empty?
53
+ when Prism::CallNode then copied_empty_seed?(node)
54
+ when *CHAINED_WRITES then empty_seed?(node.value)
55
+ else false
56
+ end
57
+ end
58
+
59
+ CHAINED_WRITES = [
60
+ Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::ClassVariableWriteNode,
61
+ Prism::GlobalVariableWriteNode, Prism::ConstantWriteNode, Prism::ConstantPathWriteNode
62
+ ].freeze
63
+ private_constant :CHAINED_WRITES
64
+
65
+ # `+""`, the unfrozen empty String the accumulator idiom spells under `# frozen_string_literal: true`, and a
66
+ # `.dup` of an empty seed (`"".dup`, `[].dup`, `{}.dup`).
67
+ def copied_empty_seed?(node)
68
+ return false unless node.arguments.nil? && node.block.nil?
69
+
70
+ case node.name
71
+ when :+@ then node.receiver.is_a?(Prism::StringNode) && node.receiver.unescaped.empty?
72
+ when :dup then empty_seed?(node.receiver)
73
+ else false
74
+ end
75
+ end
76
+
77
+ def unpinned(member)
78
+ case member
79
+ when Type::Constant
80
+ member.value.is_a?(String) && member.value.empty? ? Type::Combinator.nominal_of("String") : member
81
+ when Type::Tuple
82
+ if member.elements.empty?
83
+ Type::Combinator.nominal_of("Array",
84
+ type_args: [Type::Combinator.untyped])
85
+ else
86
+ member
87
+ end
88
+ when Type::HashShape
89
+ return member unless member.pairs.empty?
90
+
91
+ Type::Combinator.nominal_of("Hash", type_args: [Type::Combinator.untyped, Type::Combinator.untyped])
92
+ else member
93
+ end
94
+ end
95
+ private_class_method :copied_empty_seed?, :unpinned
96
+ end
97
+ end
98
+ end
99
+ end