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
@@ -46,6 +46,27 @@ module Rigor
46
46
  @untyped
47
47
  end
48
48
 
49
+ # ADR-130 WD2 — the parity predicate. Every site that keys a leniency on `untyped` asks this instead of testing
50
+ # a `Dynamic`'s facet for `Top` or comparing with {untyped}, so a value whose facet parity erases
51
+ # ({erased_facet?}) reads as `untyped` everywhere but member dispatch. `spec/rigor/type/untyped_parity_spec.rb`
52
+ # flags the raw spellings it knows outside this module. True for `Dynamic[top]` and for a plugin-interface
53
+ # value, `Dynamic[Rigor::Plugin::_Scope]`.
54
+ def untyped?(type)
55
+ return false unless type.is_a?(Dynamic)
56
+
57
+ facet = type.static_facet
58
+ facet.is_a?(Top) || erased_facet?(facet)
59
+ end
60
+
61
+ # ADR-130 WD1 — true for a `Dynamic` facet that parity reads as `top`: the `Rigor::Plugin` interface carrier
62
+ # ({Interface}). {untyped?} answers true for it, and {merge_erased_members} merges it with `Dynamic[top]`.
63
+ # `spec/rigor/type/combinator_facet_erasure_spec.rb` also drives the merge through a stub facet. An engine
64
+ # hook, not public API.
65
+ def erased_facet?(facet)
66
+ facet.is_a?(Interface)
67
+ end
68
+ private_class_method :erased_facet?
69
+
49
70
  # Wraps the static facet in a Dynamic[T] carrier. Idempotent on the static facet so
50
71
  # Dynamic[Dynamic[T]] collapses to Dynamic[T] per the value-lattice algebra.
51
72
  def dynamic(static_facet)
@@ -245,8 +266,8 @@ module Rigor
245
266
  end
246
267
 
247
268
  # `literal-string` — a `String` that is statically known to come from a source-code literal (or a
248
- # composition of literals). v0.0.9 tracks this flow through interpolation `"#{...}"`, leaving
249
- # propagation through `+` / `<<` to a later slice. Every `Constant<String>` is implicitly
269
+ # composition of literals). Interpolation `"#{...}"` and `+` / `*` composition keep
270
+ # the flow (see {literal_string_compatible?}). Every `Constant<String>` is implicitly
250
271
  # literal-string-compatible; the carrier exists for cases where the concrete value is unknown but
251
272
  # literal-ness has been established (an RBS::Extended `return: literal-string` annotation, or
252
273
  # interpolation over literal-bearing parts).
@@ -433,8 +454,7 @@ module Rigor
433
454
  # - `Type::Tuple[A, B, C]` → `Constant<0> | Constant<1> | Constant<2>`.
434
455
  # - `Type::Nominal["Hash", [K, V]]` → `K` (untyped if absent).
435
456
  # - `Type::Nominal["Array", [E]]` → `non-negative-int`.
436
- # - `Type::Constant` whose value is a Hash / Array / Range — project through the literal's
437
- # per-element keys.
457
+ # - `Type::Constant` wrapping a Range with an Integer begin — `non-negative-int`.
438
458
  #
439
459
  # Other inputs (`Top`, `Dynamic`, untyped Nominals, `Union`, `Refined`, `Difference`,
440
460
  # `Intersection`) project to `top` so the type function always returns a value — callers may narrow
@@ -456,8 +476,7 @@ module Rigor
456
476
  # - `Type::Tuple[A, B, C]` → `A | B | C`.
457
477
  # - `Type::Nominal["Hash", [K, V]]` → `V` (untyped if absent).
458
478
  # - `Type::Nominal["Array", [E]]` → `E` (untyped if absent).
459
- # - `Type::Constant` whose value is a Hash / Array / Range — union of `Constant<…>` for each
460
- # element.
479
+ # - `Type::Constant` wrapping an integer Range — the `IntegerRange` it spans.
461
480
  def value_of(type)
462
481
  case type
463
482
  when HashShape then hash_shape_values(type)
@@ -528,8 +547,7 @@ module Rigor
528
547
  # Phase A handles `Type::HashShape` (literal-key K). Phase B (slice 5) extends to `Type::Tuple`
529
548
  # (integer-index K) — `pick_of[Tuple[A, B, C], 0 | 2]` evaluates to `Tuple[A, C]`. Non-shape inputs
530
549
  # (`Type::Nominal`, etc.) return `type` unchanged ("lossy degradation"; the
531
- # `dynamic.shape.lossy-projection` diagnostic that flags the boundary lands when caller-side
532
- # diagnostic threading arrives).
550
+ # `dynamic.shape.lossy-projection` diagnostic that flags the boundary).
533
551
  def pick_of(type, keys)
534
552
  case type
535
553
  when HashShape then hash_shape_pick(type, keys)
@@ -597,8 +615,7 @@ module Rigor
597
615
  # site where the projection was authored.
598
616
  #
599
617
  # `HashShape` and `Tuple` carry shape-level information the projections honour; every other carrier
600
- # is lossy. Slice 5b wires diagnostic emission through `RbsExtended` / parser callers; this
601
- # predicate stands alone in slice 5 for unit-test coverage and future composition.
618
+ # is lossy.
602
619
  def shape_projection_lossy?(type)
603
620
  !type.is_a?(HashShape) && !type.is_a?(Tuple)
604
621
  end
@@ -792,9 +809,9 @@ module Rigor
792
809
  top
793
810
  end
794
811
 
795
- # `Type::Constant` only carries scalar literals (Integer / Float / String / Symbol / Range /
796
- # Rational / Complex / true / false / nil); Array and Hash literals become Tuple / HashShape
797
- # carriers earlier in the typer. Range is the only scalar with meaningful key/value projections.
812
+ # `Type::Constant` only carries scalar literals (`Constant::SCALAR_CLASSES`); Array and Hash literals
813
+ # become Tuple / HashShape carriers earlier in the typer. Range is the only scalar with meaningful
814
+ # key/value projections.
798
815
  def constant_keys(value)
799
816
  return non_negative_int if value.is_a?(Range) && value.begin.is_a?(Integer)
800
817
 
@@ -825,7 +842,104 @@ module Rigor
825
842
 
826
843
  return [top] if flattened.any?(Top)
827
844
 
828
- absorb_contained_members(unique_members(flattened))
845
+ absorb_contained_members(merge_erased_members(unique_members(flattened)))
846
+ end
847
+
848
+ # ADR-130 WD1 — a facet parity erases ({erased_facet?}) must not give a join a shape master's dedup would
849
+ # not: `Dynamic[_A] | Dynamic[top]` is `Dynamic[top]`, and so are the same members anywhere inside a carrier
850
+ # that holds a type ({TYPE_CHILDREN}). Members equal after {erase_facets} merge into the erased form, at the
851
+ # first one's position; a member no other matches keeps its facet, and {absorb_contained_members} compares
852
+ # the erased forms too. The facet scan runs on every join and allocates nothing (no enumerator, no array),
853
+ # so the merge costs only where a facet is present, which is nowhere until ADR-130 slice 1b.
854
+ def merge_erased_members(members)
855
+ return members if members.size < 2
856
+ return members unless members.any? { |member| facet_bearing?(member) }
857
+
858
+ erased = members.map { |member| erase_facets(member) }
859
+ counts = Hash.new(0)
860
+ erased.each { |type| counts[type] += 1 }
861
+ merged = {}
862
+ result = []
863
+ members.each_with_index do |member, index|
864
+ type = erased[index]
865
+ if counts[type] == 1
866
+ result << member
867
+ elsif !merged.key?(type)
868
+ merged[type] = true
869
+ result << type
870
+ end
871
+ end
872
+ result
873
+ end
874
+
875
+ def facet_bearing?(type)
876
+ return erased_facet?(type.static_facet) || facet_bearing?(type.static_facet) if type.is_a?(Dynamic)
877
+
878
+ readers = TYPE_CHILDREN[type.class]
879
+ return false if readers.nil?
880
+
881
+ readers.each { |reader| return true if child_facet_bearing?(type.public_send(reader)) }
882
+ false
883
+ end
884
+
885
+ def child_facet_bearing?(child)
886
+ case child
887
+ when Array then child.any? { |type| facet_bearing?(type) }
888
+ when Hash
889
+ child.each_value { |type| return true if facet_bearing?(type) }
890
+ false
891
+ else facet_bearing?(child)
892
+ end
893
+ end
894
+
895
+ # `type` with every facet {erased_facet?} accepts rewritten to `top`, through every carrier
896
+ # {TYPE_CHILDREN} lists. The identity, by reference, on a type that bears none. A facet-bearing `Dynamic`
897
+ # is never `Dynamic[top]`, so {untyped?} answers the hook for it here.
898
+ def erase_facets(type)
899
+ return type unless facet_bearing?(type)
900
+
901
+ case type
902
+ when Dynamic then untyped?(type) ? untyped : dynamic(erase_facets(type.static_facet))
903
+ when Union then union(*erase_each(type.members))
904
+ when Intersection then intersection(*erase_each(type.members))
905
+ when Tuple then tuple_of(*erase_each(type.elements))
906
+ when Nominal then nominal_of(type.class_name, type_args: erase_each(type.type_args))
907
+ when HashShape then erase_shape_facets(type)
908
+ else erase_wrapper_facets(type)
909
+ end
910
+ end
911
+
912
+ def erase_wrapper_facets(type)
913
+ case type
914
+ when Difference then Difference.new(erase_facets(type.base), erase_facets(type.removed))
915
+ when Refined then Refined.new(erase_facets(type.base), type.predicate_id)
916
+ when Maybe then Maybe.new(erase_facets(type.value_type))
917
+ when Result then Result.new(erase_facets(type.ok_type), erase_facets(type.err_type))
918
+ else erase_bearer_facets(type)
919
+ end
920
+ end
921
+
922
+ def erase_bearer_facets(type)
923
+ case type
924
+ when App then App.new(type.uri, erase_each(type.args), bound: erase_facets(type.bound))
925
+ when BoundMethod
926
+ BoundMethod.new(receiver_type: erase_facets(type.receiver_type), method_name: type.method_name)
927
+ when DataInstance then DataInstance.new(erase_values(type.members), type.class_name)
928
+ when StructInstance then StructInstance.new(erase_values(type.members), type.class_name)
929
+ # The facet itself, erased apart from its `Dynamic` (which erases whole, to `untyped`).
930
+ when Interface then Interface.new(type.name, erase_each(type.type_args))
931
+ else type
932
+ end
933
+ end
934
+
935
+ def erase_each(types) = types.map { |type| erase_facets(type) }
936
+
937
+ def erase_values(pairs) = pairs.transform_values { |type| erase_facets(type) }
938
+
939
+ def erase_shape_facets(shape)
940
+ HashShape.new(erase_values(shape.pairs),
941
+ required_keys: shape.required_keys, optional_keys: shape.optional_keys,
942
+ read_only_keys: shape.read_only_keys, extra_keys: shape.extra_keys)
829
943
  end
830
944
 
831
945
  # Drops every member another member already contains, so the join after a guard names one set
@@ -839,15 +953,30 @@ module Rigor
839
953
  # The `!absorbed_by?(other, member)` re-check costs one predicate call per pair that absorbed
840
954
  # at all, and it is what makes "both arms dropped" unrepresentable should a future clause make
841
955
  # the relation symmetric on a pair of distinct members.
956
+ #
957
+ # ADR-130 WD1 — the relation reads each member's erased form ({erase_facets}), so a facet does not keep an
958
+ # arm master absorbs: `[Dynamic[_A]] | [Dynamic[top] | Integer]` drops the first arm as master's
959
+ # `[Dynamic[top]] | [Dynamic[top] | Integer]` does. Without a facet the readings are the members themselves.
842
960
  def absorb_contained_members(members)
843
961
  return members unless members.any? { |m| absorbable_member?(m) }
844
962
 
845
963
  members = join_sequence_elements(members)
846
- members.reject do |member|
847
- absorbable_member?(member) &&
848
- members.any? do |other|
849
- !other.equal?(member) && absorbed_by?(member, other) && !absorbed_by?(other, member)
850
- end
964
+ readings = members.any? { |member| facet_bearing?(member) } ? members.map { |m| erase_facets(m) } : members
965
+ # `each_index`, not `each_with_index`: the Enumerable form allocates an iterator object on every join.
966
+ kept = []
967
+ members.each_index do |index|
968
+ member = members[index]
969
+ kept << member unless absorbable_member?(member) && absorbed_reading?(readings, index)
970
+ end
971
+ kept
972
+ end
973
+
974
+ # `any?` rather than an early `return` from an `each` block: a non-local return allocates on every absorbed
975
+ # member. Readings are distinct objects (the members were made unique, and an erased form is a new object).
976
+ def absorbed_reading?(readings, index)
977
+ reading = readings[index]
978
+ readings.any? do |wider|
979
+ !wider.equal?(reading) && absorbed_by?(reading, wider) && !absorbed_by?(wider, reading)
851
980
  end
852
981
  end
853
982
 
@@ -1044,10 +1173,18 @@ module Rigor
1044
1173
  # need not render alike (`Constant[1..2]` and `Constant[1..2.0]`), so equality is not a safe key. A
1045
1174
  # non-main Ractor may not read the module's ivar, so a pool worker renders the key every time, as every
1046
1175
  # run did before.
1176
+ #
1177
+ # ADR-130 WD1 — a member bearing a facet sorts by its erased form's rendering, so a facet does not move it:
1178
+ # `Dynamic[_A] | Dynamic[String]` keeps master's `Dynamic[String] | Dynamic[top]` order. The facet scan runs
1179
+ # only when a key is rendered, and a member without a facet renders as it always did.
1047
1180
  def sort_key(member)
1048
- return member.describe(:short) unless Ractor.main?
1181
+ return render_sort_key(member) unless Ractor.main?
1049
1182
 
1050
- @sort_keys[member] ||= member.describe(:short)
1183
+ @sort_keys[member] ||= render_sort_key(member)
1184
+ end
1185
+
1186
+ def render_sort_key(member)
1187
+ (facet_bearing?(member) ? erase_facets(member) : member).describe(:short)
1051
1188
  end
1052
1189
  end
1053
1190
 
@@ -1056,6 +1193,21 @@ module Rigor
1056
1193
  # The main Ractor's {sort_key} memo, allocated here for the same reason.
1057
1194
  @sort_keys = ObjectSpace::WeakMap.new
1058
1195
 
1196
+ # ADR-130 WD1 — the readers of every type a carrier holds, which facet erasure walks. A leaf lists none.
1197
+ # `spec/rigor/type/combinator_facet_erasure_spec.rb` fails on a `Rigor::Type` carrier missing here, and on a
1198
+ # listed carrier whose erasure leaves a facet in any of its type slots.
1199
+ TYPE_CHILDREN = Ractor.make_shareable(
1200
+ {
1201
+ Top => [], Bot => [], Constant => [], IntegerRange => [], FloatRange => [], Singleton => [],
1202
+ DataClass => [], StructClass => [],
1203
+ Dynamic => %i[static_facet], Nominal => %i[type_args], Tuple => %i[elements], HashShape => %i[pairs],
1204
+ Union => %i[members], Intersection => %i[members], Difference => %i[base removed], Refined => %i[base],
1205
+ Maybe => %i[value_type], Result => %i[ok_type err_type], App => %i[args bound],
1206
+ BoundMethod => %i[receiver_type], DataInstance => %i[members], StructInstance => %i[members],
1207
+ Interface => %i[type_args]
1208
+ }
1209
+ )
1210
+
1059
1211
  NIL_CONSTANT = Constant.new(nil)
1060
1212
  TRUE_CONSTANT = Constant.new(true)
1061
1213
  FALSE_CONSTANT = Constant.new(false)
@@ -8,9 +8,8 @@ require_relative "plain_lattice"
8
8
  module Rigor
9
9
  module Type
10
10
  # A literal carrier under ADR-3 OQ1 Option C (Hybrid). Wraps a Ruby literal value of one of the
11
- # supported immutable-ish classes. Compound literal shapes (Tuple, HashShape, Record) get dedicated
12
- # classes in later slices; Range is carried only when both static endpoints are known enough for
13
- # tuple slicing.
11
+ # supported immutable-ish classes. Compound literal shapes (Tuple, HashShape) have their own classes;
12
+ # Range is carried only when both static endpoints are known enough for tuple slicing.
14
13
  #
15
14
  # See docs/adr/4-type-inference-engine.md for the tentative answer to the open question and
16
15
  # docs/type-specification/rigor-extensions.md for the refinement neighbourhood this carrier lives in.
@@ -74,12 +73,12 @@ module Rigor
74
73
 
75
74
  # RBS supports `Literal` types for booleans, nil, integer literals (positive and negative), symbol
76
75
  # literals, and string literals. Erasing to these preserves the carrier's precision at the RBS
77
- # boundary — `Constant<64>` round-trips as `64`, not as `Integer` — and
76
+ # boundary — the constant 64 round-trips as `64`, not as `Integer` — and
78
77
  # `RbsTypeTranslator#translate_literal` already maps the parsed RBS Literal back to `Constant`.
79
78
  # Scalar carriers without RBS Literal support (Float, Range, Rational, Complex, Regexp, Pathname)
80
79
  # keep their pre-existing widen-to-class-name behaviour because RBS rejects their literal spellings
81
80
  # as syntax errors. `Range` is generic (`Range[out E]`), and a bare `Range` fails `rbs validate`, so
82
- # it carries the class of its endpoints, or `untyped` when both are nil (#1891). `Set[unchecked out A]` is
81
+ # it carries the class of its endpoints, or `untyped` when both are nil (#1891). `Set` is
83
82
  # generic too, and a set is mutable: the constant's elements are only what it held when it was built, and
84
83
  # `x << "a"` adds a member they do not name. It erases to `Set[untyped]`, as an Array or Hash literal's
85
84
  # element type is widened rather than pinned (#2177).
@@ -28,14 +28,14 @@ module Rigor
28
28
  ALLOWED_KEY_CLASSES = [Symbol, String, Integer, Float, TrueClass, FalseClass, NilClass].freeze
29
29
  EXTRA_KEY_POLICIES = %i[open closed].freeze
30
30
  POLICY_KEYWORDS = %i[required_keys optional_keys read_only_keys extra_keys].freeze
31
- # A Symbol whose text matches renders as a bare RBS record key (`lang:`) in {#erase_to_rbs};
32
- # anything else is a quoted Symbol with a fat arrow (`:"data-contrast" =>`). See {#erase_key_prefix}.
31
+ # A Symbol whose text matches renders as a bare RBS record key (`lang:`) in {#describe} and {#erase_to_rbs};
32
+ # anything else is a quoted Symbol with a fat arrow (`:"data-contrast" =>`). See {#key_prefix}.
33
33
  BARE_RECORD_KEY = /\A[A-Za-z_][A-Za-z0-9_]*[?!]?\z/
34
34
 
35
35
  attr_reader :pairs, :required_keys, :optional_keys, :read_only_keys, :extra_keys
36
36
 
37
37
  # @param pairs — ordered map of
38
- # keys to declared types. Keys MUST be Symbol or String;
38
+ # keys to declared types. Keys MUST be one of ALLOWED_KEY_CLASSES;
39
39
  # values MUST be Rigor::Type instances. The hash is duped and
40
40
  # frozen at construction; callers MUST NOT mutate the input
41
41
  # afterwards (mutation does not affect the carrier, but the
@@ -74,7 +74,7 @@ module Rigor
74
74
  return hash_erasure unless closed?
75
75
  return hash_erasure if pairs.each_key.any? { |k| !k.is_a?(Symbol) || !record_key_spellable?(k) }
76
76
 
77
- rendered = pairs.map { |k, v| "#{erase_key_prefix(k)} #{v.erase_to_rbs}" }
77
+ rendered = pairs.map { |k, v| "#{key_prefix(k)} #{v.erase_to_rbs}" }
78
78
  "{ #{rendered.join(', ')} }"
79
79
  end
80
80
 
@@ -193,46 +193,27 @@ module Rigor
193
193
  def render_entry(key, value, verbosity)
194
194
  prefix = []
195
195
  prefix << "readonly" if read_only_key?(key)
196
- rendered_key = optional_key?(key) ? "?#{render_key(key)}" : render_key(key)
197
- prefix << "#{rendered_key}#{key_separator(key)}"
196
+ prefix << key_prefix(key)
198
197
  "#{prefix.join(' ')} #{value.describe(verbosity)}"
199
198
  end
200
199
 
201
- # `describe` is a human-facing display contract (it feeds diagnostic messages, never a parser),
202
- # so it keeps the compact `"a":` form for a quoted key. Non-(Symbol|String) scalar keys render in
203
- # the natural hashrocket spelling (`1 => 2`, `1.0 => 4`, `nil => 0`) via {#key_separator}; their
204
- # `inspect` is the canonical Ruby literal, so describe stays deterministic. The RBS-erasure path
205
- # uses {#erase_key_prefix} instead, which must emit a parseable key.
206
- def render_key(key)
207
- case key
208
- when Symbol then key.to_s
209
- else key.inspect
210
- end
211
- end
212
-
213
- # Symbol / String keys keep the compact colon form (`a: 1`, `"k": 2`); every other scalar key uses
214
- # the hashrocket (`1 => 2`) — the form the user would have to write in source.
215
- def key_separator(key)
216
- case key
217
- when Symbol, String then ":"
218
- else " =>"
219
- end
220
- end
221
-
222
- # An RBS record entry's key + separator, for the {#erase_to_rbs} path (which only ever sees
223
- # Symbol keys — string-keyed shapes degrade to `Hash[...]` before reaching here). A Symbol whose
224
- # text is a plain identifier is a bare key with a colon (`lang:`, Ruby keywords like `class`
225
- # included). Any other Symbol is a quoted Symbol literal with a fat arrow (`:"data-contrast" =>`):
226
- # RBS rejects a bare non-identifier (`data-contrast:`) and a quoted key with a colon
227
- # (`"data-contrast":`), and reads `"data-contrast" =>` as the String key, so the written record
228
- # declared a key the body never has (#2176). Optional keys carry a leading `?`.
229
- def erase_key_prefix(key)
200
+ # A key and its separator as RBS spells them, with a leading `?` for an optional key. `describe`
201
+ # (the diagnostic display) and {#erase_to_rbs} (which only ever sees Symbol keys — string-keyed
202
+ # shapes degrade to `Hash[...]` before reaching here) share it, so a key reads the same in a
203
+ # message as in the signature it erases to. A Symbol whose text is a plain identifier is a bare
204
+ # key with a colon (`lang:`, Ruby keywords like `class` included). Any other Symbol is a quoted
205
+ # Symbol literal with a fat arrow (`:"data-contrast" =>`): RBS rejects a bare non-identifier
206
+ # (`data-contrast:`) and a quoted key with a colon (`"data-contrast":`), and reads
207
+ # `"data-contrast" =>` as the String key, so the written record declared a key the body never has
208
+ # (#2176). Every other key, a String included, is its `inspect` literal with a fat arrow
209
+ # (`"a-b" =>`, `1 =>`, `nil =>`): the compact `"a-b":` would read as a Symbol key in Ruby, so a
210
+ # String-vs-Symbol key mismatch in a diagnostic was unreadable (#2235).
211
+ def key_prefix(key)
230
212
  optional = optional_key?(key) ? "?" : ""
231
- if BARE_RECORD_KEY.match?(key.to_s)
232
- "#{optional}#{key}:"
233
- else
234
- "#{optional}:#{key.to_s.inspect} =>"
235
- end
213
+ return "#{optional}#{key.inspect} =>" unless key.is_a?(Symbol)
214
+ return "#{optional}#{key}:" if BARE_RECORD_KEY.match?(key.to_s)
215
+
216
+ "#{optional}:#{key.to_s.inspect} =>"
236
217
  end
237
218
 
238
219
  # `String#inspect` escapes `#{`, `#$` and `#@` as `\#`, which RBS's parser does not read back as
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
6
+ require_relative "plain_lattice"
7
+
8
+ module Rigor
9
+ module Type
10
+ # ADR-130 WD1 — an RBS interface under `Rigor::Plugin` (`_Scope`, `_Environment`, ...), as the facet of the
11
+ # `Dynamic` that carries a plugin-interface value: `Dynamic[Rigor::Plugin::_Scope]`. It appears only there.
12
+ # `Combinator.untyped?` reads such a `Dynamic` as `untyped`, and union normalisation erases it to `Dynamic[top]`
13
+ # wherever a join would otherwise keep an arm master does not, so the facet changes one behaviour: a call of a
14
+ # member the interface declares resolves through that member's RBS (`RbsDispatch`, ADR-130 WD3).
15
+ #
16
+ # `name` is spelled as `Nominal#class_name` is (`Rigor::Plugin::_Scope`), and `type_args` are the interface's
17
+ # type arguments, translated in the caller's context. Every other RBS interface still translates to
18
+ # `Dynamic[top]` (`RbsTypeTranslator`, ADR-130 WD4).
19
+ class Interface
20
+ attr_reader :name, :type_args
21
+
22
+ EMPTY_TYPE_ARGS = [].freeze
23
+ private_constant :EMPTY_TYPE_ARGS
24
+
25
+ def initialize(name, type_args = EMPTY_TYPE_ARGS)
26
+ raise ArgumentError, "name must be a String, got #{name.class}" unless name.is_a?(String)
27
+
28
+ @name = name.frozen? ? name : name.dup.freeze
29
+ @type_args = type_args.frozen? ? type_args : type_args.dup.freeze
30
+ freeze
31
+ end
32
+
33
+ def describe(verbosity = :short)
34
+ return name if type_args.empty?
35
+
36
+ "#{name}[#{type_args.map { |arg| arg.describe(verbosity) }.join(', ')}]"
37
+ end
38
+
39
+ # A facet never reaches erasure on its own (`Dynamic#erase_to_rbs` is `untyped`); spelled as RBS for a
40
+ # caller that erases the carrier directly.
41
+ def erase_to_rbs
42
+ return name if type_args.empty?
43
+
44
+ "#{name}[#{type_args.map(&:erase_to_rbs).join(', ')}]"
45
+ end
46
+
47
+ include Rigor::Type::PlainLattice
48
+
49
+ include Rigor::Type::AcceptanceRouter
50
+
51
+ include Rigor::ValueSemantics
52
+
53
+ value_fields :name, :type_args
54
+
55
+ def inspect
56
+ "#<Rigor::Type::Interface #{describe(:short)}>"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -49,7 +49,7 @@ module Rigor
49
49
  end
50
50
  end
51
51
 
52
- # ADR-1 § "RBS round-trip is lossless" + the value-lattice rule `untyped | T = untyped` (every `T`
52
+ # ADR-1 § "Erasure Must Be Conservative" + the value-lattice rule `untyped | T = untyped` (every `T`
53
53
  # is gradually consistent with `untyped`). When any union member erases to `"untyped"`, the whole
54
54
  # union erases to `"untyped"` — the RBS surface has no carrier for "Dynamic-origin alongside a
55
55
  # static facet", and the gradual-consistency contract guarantees the substitution is sound at every
data/lib/rigor/type.rb CHANGED
@@ -28,6 +28,7 @@ require_relative "type/union"
28
28
  require_relative "type/difference"
29
29
  require_relative "type/refined"
30
30
  require_relative "type/intersection"
31
+ require_relative "type/interface"
31
32
  require_relative "type/app"
32
33
  require_relative "type/result"
33
34
  require_relative "type/maybe"
@@ -22,8 +22,7 @@ module Rigor
22
22
  # Freeze the String field so the resulting Data object is `Ractor.shareable?` regardless of
23
23
  # whether the caller passed a `# frozen_string_literal: true` constant or a dynamically built
24
24
  # String. The same discipline applies to every other TypeNode value object — they live in the
25
- # parser's hot path and are the natural carriers to flow through future Ractor boundaries
26
- # (see CURRENT_WORK Open Items #8).
25
+ # parser's hot path and are the natural carriers to flow through future Ractor boundaries.
27
26
  super(name: name.frozen? ? name : name.dup.freeze)
28
27
  end
29
28
  end
@@ -16,7 +16,7 @@ module Rigor
16
16
  # resolver in the chain plus the built-in registry through the
17
17
  # same entry point.
18
18
  #
19
- # Constructed once per `Analysis::Runner.run` from
19
+ # Built by `Environment#build_name_scope` from
20
20
  # `Plugin::Registry#type_node_resolvers`. The chain is
21
21
  # immutable and re-entrant; the parser may consult it many
22
22
  # times for the same node.
@@ -7,8 +7,7 @@ module Rigor
7
7
  # `Pick[T, "name"]`, `pick_of[Shape, "a" | "b"]`, and downstream
8
8
  # plugin resolvers that accept literal key selectors.
9
9
  #
10
- # ADR-13 follow-up (`docs/CURRENT_WORK.md` engineering item
11
- # #2): the RBS::Extended grammar previously could not tokenise
10
+ # ADR-13 follow-up: the RBS::Extended grammar previously could not tokenise
12
11
  # `"name"` inside a type-arg position. The resolver translates
13
12
  # this node to a `Type::Constant` carrying the string value.
14
13
  # Slice 1 supports double-quoted strings without escape
@@ -7,8 +7,7 @@ module Rigor
7
7
  # `Pick[T, :name]`, `pick_of[Shape, :a | :b]`, and downstream
8
8
  # plugin resolvers that accept literal key selectors.
9
9
  #
10
- # ADR-13 follow-up (`docs/CURRENT_WORK.md` engineering item
11
- # #2): the RBS::Extended grammar previously could not tokenise
10
+ # ADR-13 follow-up: the RBS::Extended grammar previously could not tokenise
12
11
  # `:name` inside a type-arg position; this addition closes the
13
12
  # gap so `ImportedRefinements.parse` produces a uniform AST.
14
13
  # The resolver translates this node to a `Type::Constant`
@@ -5,8 +5,7 @@ module Rigor
5
5
  # Union AST node. Carries the nodes of a `T1 | T2 | …` union
6
6
  # expression authored at type-arg position.
7
7
  #
8
- # ADR-13 follow-up (`docs/CURRENT_WORK.md` engineering item
9
- # #2): together with {SymbolLiteral} / {StringLiteral} this
8
+ # ADR-13 follow-up: together with {SymbolLiteral} / {StringLiteral} this
10
9
  # closes the parser-side gap that prevented `Pick[T, :a | :b]`
11
10
  # / `Pick[T, "a" | "b"]` from tokenising. The resolver pass
12
11
  # recurses through each node and folds them via
@@ -16,10 +15,8 @@ module Rigor
16
15
  # shadowing `Data#members`, which is the introspection method
17
16
  # every `Data.define` mix-in inherits.
18
17
  #
19
- # Nodes are themselves any of the {TypeNode} carriers — the
20
- # parser builds a flat list (left-associative); nested
21
- # `Union` nodes are permitted but the parser does not
22
- # synthesise them by itself.
18
+ # The parser builds a flat list (left-associative); the constructor
19
+ # rejects a nested `Union` node.
23
20
  class Union < Data.define(:nodes)
24
21
  def initialize(nodes:)
25
22
  unless nodes.is_a?(Array) && nodes.size >= 2 && nodes.all? { |m| valid_member?(m) }
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module TypeOfMemo
5
+ # G1's stage-4 form (ADR-129 WD5), mixed into {TypeOfMemo}'s singleton: on a would-be hit with
6
+ # `TypeOfMemo.enable!(serve: true)`, the memo serves the entry and checks it against a recomputation. For specs
7
+ # and audit tools; the CLI's `RIGOR_TYPE_OF_SHADOW=1` never serves.
8
+ module Serving
9
+ EMPTY = [].freeze
10
+ private_constant :EMPTY
11
+
12
+ private
13
+
14
+ # G1's stage-4 form (ADR-129 WD5) on one would-be hit:
15
+ # 1. recompute on the same scope objects with the memo suspended, in a capture and a priors window, and record
16
+ # the final state of every entry either side touches; a recomputation that touched a Resource is a mismatch
17
+ # and its answer stands;
18
+ # 2. undo the recomputation's Table writes and drop its journal records, which the served entry replaces;
19
+ # 3. serve the entry: validate its reads, merge them into the enclosing captures, replay its writes, and compare
20
+ # the touched entries and the type with the recorded ones. A recomputation that raises where an entry exists
21
+ # is a mismatch too.
22
+ def serve_hit(state, table, phase, scope, node, entry, &)
23
+ stored, reads, writes = entry.is_a?(Array) ? entry : [entry, EMPTY, EMPTY]
24
+ count(phase, :checked)
25
+ mark = state.journal_size
26
+ fresh, priors, refused = recompute(state, table, phase, scope, node, stored, &)
27
+ if refused
28
+ judge(state, phase, scope, node, stored, fresh, writes, priors, refused)
29
+ return fresh
30
+ end
31
+
32
+ serve(state, phase, scope, node, stored, fresh, [reads, writes, priors, mark])
33
+ end
34
+
35
+ # Steps 2 and 3 of {#serve_hit}. An error in them (a replay into a store the entry outlived) is a mismatch,
36
+ # counted rather than raised into the typing path's rescues. The recomputation's type is answered; the tables may
37
+ # hold a partial replay by then, which the counted mismatch already fails.
38
+ def serve(state, phase, scope, node, stored, fresh, slices)
39
+ reads, writes, priors, mark = slices
40
+ live = touched(writes, priors).map { |slot, key| [slot, key, state.entry(slot, key)] }
41
+ state.undo(priors)
42
+ state.truncate_journal(mark)
43
+ kinds = []
44
+ kinds << :revalidate if state.stale_read(reads)
45
+ state.merge_reads(reads)
46
+ state.replay(writes)
47
+ served(state, phase, scope, node, kinds, stored, fresh, live)
48
+ stored
49
+ rescue StandardError => e
50
+ report(phase, scope, node, [:serve_error, "#{e.class}: #{e.message[0, 120]}"], stored, fresh, [])
51
+ fresh
52
+ end
53
+
54
+ def recompute(state, table, phase, scope, node, stored)
55
+ state.open_capture
56
+ state.open_priors
57
+ table.suspended += 1
58
+ returned = false
59
+ begin
60
+ fresh = yield
61
+ returned = true
62
+ ensure
63
+ table.suspended -= 1
64
+ priors = state.close_priors
65
+ refused, = state.close_capture
66
+ report(phase, scope, node, [:raised], stored, nil, []) unless returned
67
+ end
68
+ [fresh, priors, refused]
69
+ end
70
+
71
+ # Every entry the served writes or the recomputation's writes touch, each once.
72
+ def touched(writes, priors)
73
+ seen = TypingState::KeyIndex.new
74
+ keys = []
75
+ [writes, priors].each do |slice|
76
+ slice.each_slice(3) do |slot, key, _|
77
+ next if seen.key?(slot, key)
78
+
79
+ seen.store(slot, key, true)
80
+ keys << [slot, key]
81
+ end
82
+ end
83
+ keys
84
+ end
85
+
86
+ def served(state, phase, scope, node, kinds, stored, fresh, live) # rubocop:disable Metrics/ParameterLists
87
+ kinds << :type unless same?(fresh, stored)
88
+ diffs = live.filter_map do |slot, key, want|
89
+ now = state.entry(slot, key)
90
+ [slot, key, want, now] unless same?(want, now)
91
+ end
92
+ kinds << :table unless diffs.empty?
93
+ report(phase, scope, node, kinds, stored, fresh, diffs) unless kinds.empty?
94
+ rescue StandardError => e
95
+ report(phase, scope, node, [:judge_error, "#{e.class}: #{e.message[0, 120]}"], stored, fresh, [])
96
+ end
97
+ end
98
+ end
99
+ end