rigortype 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/core_overlay/enumerable.rbs +51 -0
  4. data/data/core_overlay/enumerator.rbs +84 -0
  5. data/data/core_overlay/hash_rbs3.rbs +41 -0
  6. data/data/core_overlay/process.rbs +40 -0
  7. data/data/core_overlay/string_io.rbs +33 -0
  8. data/data/effects/core.yml +3 -3
  9. data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
  10. data/docs/handbook/03-narrowing.md +95 -10
  11. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  12. data/docs/handbook/07-rbs-and-extended.md +16 -11
  13. data/docs/handbook/10-sorbet.md +9 -10
  14. data/docs/handbook/11-sig-gen.md +454 -13
  15. data/docs/manual/02-cli-reference.md +63 -2
  16. data/docs/manual/03-configuration.md +7 -0
  17. data/docs/manual/04-diagnostics.md +4 -0
  18. data/docs/manual/07-plugins.md +1 -1
  19. data/docs/manual/10-mcp-server.md +3 -2
  20. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  21. data/docs/manual/19-effect-labels.md +10 -0
  22. data/docs/manual/plugins/README.md +7 -0
  23. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  24. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  25. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  26. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  27. data/docs/manual/plugins/rigor-activejob.md +7 -0
  28. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  29. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  30. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  31. data/docs/manual/plugins/rigor-grape.md +106 -0
  32. data/docs/manual/plugins/rigor-graphql.md +23 -2
  33. data/docs/manual/plugins/rigor-pundit.md +8 -1
  34. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  35. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  36. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  37. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  38. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  39. data/exe/rigor +19 -4
  40. data/lib/rigor/analysis/baseline.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  42. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  43. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  49. data/lib/rigor/analysis/check_rules.rb +520 -49
  50. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  51. data/lib/rigor/analysis/fact_store.rb +9 -0
  52. data/lib/rigor/analysis/incremental.rb +26 -0
  53. data/lib/rigor/analysis/incremental_session.rb +36 -4
  54. data/lib/rigor/analysis/project_scan.rb +11 -1
  55. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  56. data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
  57. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  58. data/lib/rigor/analysis/run_cache_key.rb +20 -11
  59. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
  60. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  61. data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
  62. data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
  63. data/lib/rigor/analysis/runner.rb +278 -20
  64. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  65. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  66. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  67. data/lib/rigor/analysis/template_units.rb +399 -0
  68. data/lib/rigor/analysis/worker_session.rb +53 -10
  69. data/lib/rigor/bleeding_edge.rb +0 -2
  70. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  71. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  72. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  73. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  74. data/lib/rigor/cache/descriptor.rb +6 -1
  75. data/lib/rigor/cache/engine_source.rb +29 -1
  76. data/lib/rigor/cache/incremental_snapshot.rb +33 -1
  77. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  78. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  79. data/lib/rigor/cache/store.rb +0 -1
  80. data/lib/rigor/ci_detector.rb +1 -0
  81. data/lib/rigor/cli/doc_links.rb +1 -1
  82. data/lib/rigor/cli/docs_command.rb +4 -4
  83. data/lib/rigor/cli/plugin_command.rb +3 -3
  84. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  85. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  86. data/lib/rigor/cli/skill_command.rb +1 -1
  87. data/lib/rigor/cli/skill_describe.rb +0 -1
  88. data/lib/rigor/cli/type_of_command.rb +19 -5
  89. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  90. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  91. data/lib/rigor/cli.rb +21 -3
  92. data/lib/rigor/configuration/severity_profile.rb +19 -3
  93. data/lib/rigor/configuration.rb +66 -3
  94. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  95. data/lib/rigor/effects/attribution.rb +11 -2
  96. data/lib/rigor/effects/callee_rule.rb +368 -0
  97. data/lib/rigor/effects/catalog.rb +7 -4
  98. data/lib/rigor/effects/collector.rb +11 -5
  99. data/lib/rigor/effects/config_envelopes.rb +9 -2
  100. data/lib/rigor/effects/definition_context.rb +179 -0
  101. data/lib/rigor/effects/effect_table.rb +11 -3
  102. data/lib/rigor/effects/envelope_check.rb +1 -1
  103. data/lib/rigor/effects/envelope_index.rb +15 -0
  104. data/lib/rigor/effects/file_collection.rb +59 -5
  105. data/lib/rigor/effects/framework_units.rb +1 -1
  106. data/lib/rigor/effects/identity.rb +16 -0
  107. data/lib/rigor/effects/local_ownership.rb +38 -12
  108. data/lib/rigor/effects/method_key.rb +21 -0
  109. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  110. data/lib/rigor/effects/plugin_facts.rb +43 -31
  111. data/lib/rigor/effects/propagator.rb +295 -16
  112. data/lib/rigor/effects/registry.rb +1 -1
  113. data/lib/rigor/effects/scanner.rb +121 -72
  114. data/lib/rigor/effects/signature_sources.rb +1 -1
  115. data/lib/rigor/effects/snapshot.rb +2 -1
  116. data/lib/rigor/effects/summary.rb +27 -4
  117. data/lib/rigor/effects/unit_scan.rb +385 -30
  118. data/lib/rigor/effects/visibility.rb +101 -0
  119. data/lib/rigor/environment/lockfile_resolver.rb +17 -0
  120. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  121. data/lib/rigor/environment/member_consistency.rb +298 -0
  122. data/lib/rigor/environment/rbs_loader.rb +399 -133
  123. data/lib/rigor/environment.rb +103 -38
  124. data/lib/rigor/hashing/xxh3.rb +264 -0
  125. data/lib/rigor/inference/acceptance.rb +139 -9
  126. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  127. data/lib/rigor/inference/block_call_timing.rb +338 -0
  128. data/lib/rigor/inference/block_parameter_binder.rb +73 -27
  129. data/lib/rigor/inference/block_repetition.rb +71 -0
  130. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  131. data/lib/rigor/inference/budget_trace.rb +2 -1
  132. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  133. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  134. data/lib/rigor/inference/captured_locals.rb +387 -15
  135. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  136. data/lib/rigor/inference/content_join.rb +200 -27
  137. data/lib/rigor/inference/def_return_typer.rb +11 -7
  138. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  139. data/lib/rigor/inference/element_read_widening.rb +22 -10
  140. data/lib/rigor/inference/error_info.rb +196 -0
  141. data/lib/rigor/inference/expression_typer.rb +1532 -496
  142. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  143. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  144. data/lib/rigor/inference/global_write_census.rb +239 -0
  145. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  146. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  147. data/lib/rigor/inference/index_write_widening.rb +16 -3
  148. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  149. data/lib/rigor/inference/jump_targets.rb +82 -0
  150. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  151. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  152. data/lib/rigor/inference/last_line.rb +340 -0
  153. data/lib/rigor/inference/last_status.rb +144 -0
  154. data/lib/rigor/inference/macro_block_self_type.rb +167 -17
  155. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  156. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  157. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  158. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  159. data/lib/rigor/inference/match_rebinding.rb +392 -0
  160. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  161. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  162. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  163. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  164. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  165. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  166. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  167. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  168. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  169. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
  170. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  171. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  172. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  173. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  174. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  175. data/lib/rigor/inference/multi_target_binder.rb +340 -52
  176. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  177. data/lib/rigor/inference/mutation_widening.rb +70 -48
  178. data/lib/rigor/inference/narrowing.rb +540 -120
  179. data/lib/rigor/inference/operand_effects.rb +167 -0
  180. data/lib/rigor/inference/operand_walk.rb +88 -0
  181. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  182. data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
  183. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  184. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  185. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  186. data/lib/rigor/inference/receiver_alias.rb +90 -1
  187. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  188. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  189. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  190. data/lib/rigor/inference/return_barrier.rb +54 -0
  191. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  192. data/lib/rigor/inference/scope_indexer.rb +4615 -551
  193. data/lib/rigor/inference/statement_evaluator.rb +3176 -490
  194. data/lib/rigor/inference/stored_block_call.rb +54 -0
  195. data/lib/rigor/inference/string_mutation.rb +44 -7
  196. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  197. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  198. data/lib/rigor/language_server/debouncer.rb +0 -1
  199. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  200. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  201. data/lib/rigor/language_server/project_context.rb +5 -3
  202. data/lib/rigor/mcp/server.rb +2 -1
  203. data/lib/rigor/plugin/base.rb +168 -5
  204. data/lib/rigor/plugin/box_probe.rb +91 -0
  205. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  206. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  207. data/lib/rigor/plugin/loader.rb +2 -1
  208. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  209. data/lib/rigor/plugin/manifest.rb +71 -10
  210. data/lib/rigor/plugin/registry.rb +35 -1
  211. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  212. data/lib/rigor/plugin/template_unit.rb +196 -0
  213. data/lib/rigor/plugin.rb +1 -0
  214. data/lib/rigor/protection/discovery_seed.rb +3 -1
  215. data/lib/rigor/protection/kill_signature.rb +0 -1
  216. data/lib/rigor/protection/mutation_cache.rb +1 -2
  217. data/lib/rigor/rbs_extended.rb +27 -0
  218. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  219. data/lib/rigor/reflection/constant_path.rb +19 -6
  220. data/lib/rigor/reflection.rb +72 -105
  221. data/lib/rigor/scope/discovery_index.rb +135 -2
  222. data/lib/rigor/scope.rb +859 -49
  223. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  224. data/lib/rigor/sig_gen/classification.rb +22 -5
  225. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  226. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  227. data/lib/rigor/sig_gen/generator.rb +558 -31
  228. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  229. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  230. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  231. data/lib/rigor/sig_gen/renderer.rb +124 -9
  232. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  233. data/lib/rigor/sig_gen/write_result.rb +19 -3
  234. data/lib/rigor/sig_gen/writer.rb +166 -35
  235. data/lib/rigor/sig_gen.rb +3 -0
  236. data/lib/rigor/signature_path_audit.rb +1 -1
  237. data/lib/rigor/source/node_walker.rb +0 -3
  238. data/lib/rigor/source/parameter_envelope.rb +72 -0
  239. data/lib/rigor/source.rb +1 -0
  240. data/lib/rigor/type/combinator.rb +88 -12
  241. data/lib/rigor/type/difference.rb +1 -0
  242. data/lib/rigor/type/hash_shape.rb +1 -1
  243. data/lib/rigor/type/refined.rb +1 -0
  244. data/lib/rigor/version.rb +1 -1
  245. data/lib/rigor.rb +1 -0
  246. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  247. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  248. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  249. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  250. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  251. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  252. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  253. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  254. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  255. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  256. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  257. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  258. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  259. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  260. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  261. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  262. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  263. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  264. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  265. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  266. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  267. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  268. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  269. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  270. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  271. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  272. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  273. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  274. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  275. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  276. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  277. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  278. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  279. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  280. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  281. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  282. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  283. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  284. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  285. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  286. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  287. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  288. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  289. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  290. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  291. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  292. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  293. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  294. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  295. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  296. data/sig/rigor/analysis/baseline.rbs +69 -7
  297. data/sig/rigor/analysis/fact_store.rbs +1 -1
  298. data/sig/rigor/analysis/project_scan.rbs +74 -0
  299. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  300. data/sig/rigor/effects/effect_table.rbs +60 -0
  301. data/sig/rigor/effects/envelope.rbs +97 -0
  302. data/sig/rigor/effects/envelope_index.rbs +33 -0
  303. data/sig/rigor/effects/file_collection.rbs +81 -0
  304. data/sig/rigor/effects/label.rbs +26 -0
  305. data/sig/rigor/effects/label_set.rbs +46 -0
  306. data/sig/rigor/effects/method_key.rbs +24 -0
  307. data/sig/rigor/effects/origin.rbs +50 -0
  308. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  309. data/sig/rigor/effects/registry.rbs +68 -0
  310. data/sig/rigor/effects/summary.rbs +50 -0
  311. data/sig/rigor/effects/taint_cause.rbs +12 -0
  312. data/sig/rigor/environment.rbs +12 -10
  313. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  314. data/sig/rigor/inference.rbs +6 -4
  315. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  316. data/sig/rigor/plugin/base.rbs +30 -7
  317. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  318. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  319. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  320. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  321. data/sig/rigor/plugin/io_boundary.rbs +1 -1
  322. data/sig/rigor/plugin/loader.rbs +3 -3
  323. data/sig/rigor/plugin/manifest.rbs +50 -11
  324. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  325. data/sig/rigor/plugin/registry.rbs +62 -1
  326. data/sig/rigor/plugin.rbs +1 -1
  327. data/sig/rigor/rbs_extended.rbs +1 -1
  328. data/sig/rigor/reflection.rbs +7 -6
  329. data/sig/rigor/scope.rbs +135 -20
  330. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  331. data/sig/rigor/source.rbs +4 -4
  332. data/sig/rigor/testing.rbs +10 -4
  333. data/sig/rigor/type.rbs +6 -0
  334. data/sig/rigor.rbs +38 -20
  335. data/skills/rigor-ask/SKILL.md +8 -5
  336. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  337. data/skills/rigor-ci-setup/SKILL.md +17 -21
  338. data/skills/rigor-doctor/SKILL.md +24 -22
  339. data/skills/rigor-doctor/references/01-checks.md +97 -33
  340. data/skills/rigor-editor-setup/SKILL.md +6 -4
  341. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  342. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  343. data/skills/rigor-next-steps/SKILL.md +4 -2
  344. data/skills/rigor-plugin-author/SKILL.md +19 -23
  345. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  346. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  347. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  348. data/skills/rigor-plugin-review/SKILL.md +6 -4
  349. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  350. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  351. data/skills/rigor-project-init/SKILL.md +9 -7
  352. data/skills/rigor-project-init/references/01-detect.md +13 -9
  353. data/skills/rigor-project-init/references/02-configure.md +33 -8
  354. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  355. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  356. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  357. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  358. data/skills/rigor-type-oracle/SKILL.md +4 -6
  359. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  360. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  361. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  362. data/skills/rigor-upgrade/SKILL.md +13 -8
  363. metadata +108 -1
@@ -16,6 +16,7 @@ require_relative "environment/lockfile_resolver"
16
16
  require_relative "environment/installed_gem_set"
17
17
  require_relative "environment/rbs_collection_discovery"
18
18
  require_relative "plugin/isolation"
19
+ require_relative "cache/engine_source"
19
20
  require_relative "environment/rbs_coverage_report"
20
21
  require_relative "inference/synthetic_method_index"
21
22
  require_relative "inference/project_patched_methods"
@@ -273,7 +274,7 @@ module Rigor
273
274
  bundler_lockfile: nil,
274
275
  rbs_collection_lockfile: nil, rbs_collection_auto_detect: false,
275
276
  synthetic_method_index: nil, project_patched_methods: nil,
276
- source_files: [])
277
+ source_files: [], locked_gems: nil)
277
278
  resolved_paths = signature_paths || default_signature_paths(root)
278
279
  # O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
279
280
  # root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
@@ -284,7 +285,10 @@ module Rigor
284
285
  # to the project root), use the locked gem set to filter the discovered `sig/` directories. Stale
285
286
  # gems in the bundle install tree (out-of-band installs, version drift after a `bundle update`) are
286
287
  # silently dropped so only gems the project actually declares contribute RBS.
287
- locked = LockfileResolver.locked_gems(
288
+ #
289
+ # #1064 — `locked_gems:` is that same map resolved by the caller. The Ractor pool passes it because
290
+ # Bundler's parser cannot run in a non-main Ractor; every other caller leaves it nil and resolves here.
291
+ locked = locked_gems || LockfileResolver.locked_gems(
288
292
  lockfile_path: bundler_lockfile,
289
293
  project_root: root,
290
294
  auto_detect: bundler_auto_detect
@@ -358,13 +362,14 @@ module Rigor
358
362
  # the bundle walk and `rbs collection install` all stay authoritative.
359
363
  deferred_signature_paths: plugin_sig_paths
360
364
  )
361
- # Issue #824 / ADR-32 WD13 — one `:info` per inline member the loader stood down against the
362
- # project's own `sig/`. Recorded HERE because this is where the reporter is: the run's
363
- # `source_rbs_synthesis` stream already carries WD12's "parsed but not honoured" rows, drains out of
364
- # pool workers, and is regenerated (never cached) on every run, so the member-level stand-down needs
365
- # no plumbing of its own. Costs nothing for a project with no inline RBS: the loader's reader
366
- # short-circuits on an empty `virtual_rbs`.
367
- record_inline_member_standdowns(loader, source_rbs_synthesis_reporter, root)
365
+ # Issues #824 / #1075 — the rows ADR-112 WD5's consistency rule reports for members the project's
366
+ # own `sig/` and an inline source both declare: an error per contradiction (or per refinement that
367
+ # exceeds its own declared type), an `:info` per inline member dropped undecided. Recorded HERE
368
+ # because this is where the reporter is: the run's `source_rbs_synthesis` stream already carries
369
+ # WD12's "parsed but not honoured" rows, drains out of pool workers, and is regenerated (never cached)
370
+ # on every run, so the member-level outcome needs no plumbing of its own. Costs nothing for a project
371
+ # with no inline RBS: the loader's reader short-circuits on an empty `virtual_rbs`.
372
+ record_member_consistency(loader, source_rbs_synthesis_reporter, root)
368
373
  # ADR-20 slice 2c + 2e — seed hkt_registry with the bundled builtins. The Environment's
369
374
  # `#hkt_registry` getter then LAZILY merges in the RBS env scan on first call so fast paths that
370
375
  # don't consult HKT (e.g. `rigor check --cache-stats --no-stats`) don't pay the eager env-build cost
@@ -492,7 +497,10 @@ module Rigor
492
497
  #
493
498
  # WD5 — when `cache_store` is supplied, each (file, plugin) synthesizer call is memoised through
494
499
  # `Cache::Store`. The cache key composes the file's content SHA with the plugin's `PluginEntry` (id +
495
- # version + config_hash) so a config change or content change invalidates the entry automatically.
500
+ # version + config_hash) so a config change or content change invalidates the entry automatically, and
501
+ # with the engine's source identity (issue #1009): a plugin's manifest version does not move when a
502
+ # checkout edits its synthesizer, and a stale string here also keeps the `rbs.virtual_rbs` env key warm,
503
+ # so the new build's rules would read the old build's RBS.
496
504
  def collect_virtual_rbs(plugin_registry, source_files, cache_store, reporter)
497
505
  return [] if plugin_registry.nil?
498
506
 
@@ -523,6 +531,8 @@ module Rigor
523
531
  return invoke_synthesizer_safely(callable, path) unless File.file?(path)
524
532
 
525
533
  descriptor = build_synthesizer_cache_descriptor(plugin, path)
534
+ return invoke_synthesizer_safely(callable, path) if descriptor.nil?
535
+
526
536
  cache_store.fetch_or_compute(
527
537
  producer_id: SYNTHESIZER_CACHE_PRODUCER_ID,
528
538
  params: {},
@@ -565,29 +575,78 @@ module Rigor
565
575
  reporter&.record(plugin_id: plugin.manifest.id, path: path, message: message.to_s, kind: kind)
566
576
  end
567
577
 
568
- # ADR-32 WD13 — turn each {Environment::RbsLoader#inline_member_standdowns} record into a WD12
569
- # `:not_honoured` entry, so the run's `plugin.rbs-inline.source-rbs-annotation-not-honoured` row names
570
- # BOTH sources and which one won. WD12's rule is what makes this necessary rather than optional: an
571
- # annotation Rigor parses and does not honour is reported, never swallowed — and a silent per-member
572
- # strip is exactly the shape that rule forbids.
578
+ # ADR-112 WD5 / issue #1075 — turn each reported {MemberConsistency::Record} into a row on the
579
+ # synthesis stream. A contradiction, or a refinement outside its own declared type, is a `:contradiction`
580
+ # entry ({Analysis::CheckRules::RULE_CONTRADICTING_SIGNATURE}, an error), positioned at the `.rbs`
581
+ # member when there is one: that file is real and its line is exact, where the inline member's position
582
+ # lives in the synthesized RBS buffer, a document nobody has. An undecided pair keeps ADR-32 WD12's
583
+ # `:not_honoured` row at the annotated `.rb` (line 1, as every entry on that stream is): the inline
584
+ # signature was parsed and dropped, and WD12 forbids swallowing that. The silent outcomes — equal, or
585
+ # merged to the more precise side — record nothing.
573
586
  #
574
- # The row is positioned at the annotated `.rb` (line 1, as every entry on this stream is): the member's
575
- # own position lives in the synthesized RBS buffer, which describes a document nobody has, and the
576
- # ADR-54 environment cache drops positions anyway, so a line read off it would differ warm and cold.
577
- def record_inline_member_standdowns(loader, reporter, root)
587
+ # The records are derived with a {MemberConsistency::RbsProof}, so a contradiction is proven from the
588
+ # RBS hierarchy of core and stdlib classes, never from the analyzer process's loaded constants.
589
+ def record_member_consistency(loader, reporter, root)
578
590
  return if reporter.nil?
579
591
 
580
- loader.inline_member_standdowns.each do |class_name, method_name, kind, signature_path, virtual_name|
581
- plugin_id, path = split_virtual_source_name(virtual_name)
582
- next if path.nil?
583
-
584
- reporter.record(
585
- plugin_id: plugin_id, path: path, kind: :not_honoured,
586
- message: inline_member_standdown_message(class_name, method_name, kind, signature_path, root)
587
- )
592
+ proof = MemberConsistency::RbsProof.new(loader)
593
+ loader.member_consistency(proof: proof).each do |record|
594
+ case record.outcome
595
+ when :contradiction then record_member_contradiction(reporter, record, root)
596
+ when :refinement then record_refinement_contradiction(reporter, record, root)
597
+ when :undecided then record_member_undecided(reporter, record, root)
598
+ end
588
599
  end
589
600
  end
590
601
 
602
+ def record_member_contradiction(reporter, record, root)
603
+ plugin_id, path = split_virtual_source_name(record.virtual_name)
604
+ return if path.nil?
605
+
606
+ reporter.record(
607
+ plugin_id: plugin_id, path: path_relative_to(record.signature_path, root), line: record.signature_line,
608
+ kind: :contradiction,
609
+ message: "`#{member_label(record)}` is declared here and by an inline annotation in " \
610
+ "`#{path_relative_to(path, root)}`, and the two contradict: #{record.detail}. Rigor reads " \
611
+ "this `.rbs` declaration. Regenerate the signature, or fix the annotation, so that one " \
612
+ "of the two refines the other."
613
+ )
614
+ end
615
+
616
+ def record_refinement_contradiction(reporter, record, root)
617
+ plugin_id, path = split_virtual_source_name(record.virtual_name)
618
+ path = record.signature_path if path.nil?
619
+ return if path.nil?
620
+
621
+ reporter.record(
622
+ plugin_id: plugin_id.to_s, path: path_relative_to(path, root), line: record.signature_line,
623
+ kind: :contradiction,
624
+ message: "`#{member_label(record)}` refines its own signature to a type outside it: " \
625
+ "#{record.detail}. A `rigor:v1:` refinement may narrow the ordinary RBS contract, never " \
626
+ "contradict it, so one of the two is wrong."
627
+ )
628
+ end
629
+
630
+ def record_member_undecided(reporter, record, root)
631
+ plugin_id, path = split_virtual_source_name(record.virtual_name)
632
+ return if path.nil?
633
+
634
+ reporter.record(
635
+ plugin_id: plugin_id, path: path, kind: :not_honoured,
636
+ message: "`#{member_label(record)}` is also declared in " \
637
+ "`#{path_relative_to(record.signature_path, root)}`, and Rigor cannot tell which of the two " \
638
+ "is the more precise (#{record.detail}), so the `.rbs` declaration binds and the inline " \
639
+ "signature was dropped. Left to collide the two would fail the class's definition build, " \
640
+ "and every call on it — real methods and typos alike — would read `Dynamic[top]`. Make " \
641
+ "one of the two a refinement of the other, or remove one, to settle which binds."
642
+ )
643
+ end
644
+
645
+ def member_label(record)
646
+ separator = record.kind == :singleton ? "." : "#"
647
+ "#{record.class_name}#{separator}#{record.method_name}"
648
+ end
649
+
591
650
  # Splits a virtual buffer name back into the pair {.collect_virtual_rbs} composed it from
592
651
  # (`virtual:<plugin id>:<source path>`). Limited to three fields so a path carrying a colon survives.
593
652
  # Returns `[nil, nil]` for a name in any other shape — a synthesizer channel this method does not own.
@@ -598,16 +657,6 @@ module Rigor
598
657
  [plugin_id, path]
599
658
  end
600
659
 
601
- def inline_member_standdown_message(class_name, method_name, kind, signature_path, root)
602
- separator = kind == :singleton ? "." : "#"
603
- "`#{class_name}#{separator}#{method_name}` is also declared in " \
604
- "`#{path_relative_to(signature_path, root)}`, and an explicit `.rbs` declaration wins over an " \
605
- "inline annotation for the same member, so the inline signature was dropped. Left to collide the " \
606
- "two would fail the class's definition build, and every call on it — real methods and typos " \
607
- "alike — would read `Dynamic[top]`. Remove one of the two declarations to make the inline " \
608
- "annotation bind."
609
- end
610
-
611
660
  def path_relative_to(path, root)
612
661
  prefix = "#{root}#{File::SEPARATOR}"
613
662
  path.to_s.start_with?(prefix) ? path.to_s.delete_prefix(prefix) : path.to_s
@@ -631,8 +680,13 @@ module Rigor
631
680
  comparator: :digest,
632
681
  value: synthesizer_input_digest(path)
633
682
  )],
634
- plugins: [plugin.plugin_entry]
683
+ plugins: [plugin.plugin_entry],
684
+ configs: Cache::EngineSource.key_config_entries
635
685
  )
686
+ rescue Cache::EngineSource::Unavailable
687
+ # An engine that cannot be identified must not be keyed by its inputs alone (issue #1009): nil runs the
688
+ # synthesizer uncached rather than serving an entry another build may have written.
689
+ nil
636
690
  end
637
691
 
638
692
  def synthesizer_input_digest(path)
@@ -696,6 +750,17 @@ module Rigor
696
750
  end
697
751
  end
698
752
 
753
+ # Issue #1362 — the translated type of the RBS declaration of the global variable `name` (`$stdout: IO` answers
754
+ # `Nominal[IO]`), or nil when no signature declares it or the environment carries no RBS loader. With
755
+ # `builtin: true` it answers only for a declaration in Ruby's own core or stdlib signatures, a global the
756
+ # interpreter sets; a project's or a gem's declaration then answers nil.
757
+ def global_for_name(name, builtin: false)
758
+ return nil if rbs_loader.nil?
759
+ return nil if builtin && !rbs_loader.core_or_stdlib_global?(name)
760
+
761
+ rbs_loader.global_type(name)
762
+ end
763
+
699
764
  # Returns true when the constant name is known to either the static registry or the RBS loader. Useful
700
765
  # for callers that only need a presence check without materialising a type carrier.
701
766
  def class_known?(name)
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Hashing
5
+ # Pure-Ruby XXH3-64 (seed 0, default secret) — the content hash for the `rigor lens` anchors of
6
+ # ADR-113 WD6, landed ahead of `rigor lens` itself (#1083). A port of the upstream xxHash scalar
7
+ # path (`xxhash.h`'s `XXH3_64bits` with `XXH3_kSecret`), pinned by the upstream sanity-check
8
+ # vectors and cross-checked against lisplens's `xxhash-rust` output; see
9
+ # `spec/rigor/hashing/xxh3_spec.rb`.
10
+ #
11
+ # Pure Ruby per ADR-31: a native `xxhash` gem is a supply-chain addition to hash a few hundred
12
+ # bytes per call. Only the seedless/default-secret variant is implemented — the `rigor lens`
13
+ # anchors are the sole consumer, and they match lisplens ADR-0008's form exactly:
14
+ #
15
+ # - {.anchor_hash} is `xxh3_64 & 0xffff` printed as 4 lowercase hex digits (a per-row anchor).
16
+ # - {.file_hash} is the full 64-bit value printed as 16 lowercase hex digits (a file-level guard).
17
+ #
18
+ # Every arithmetic op below is modulo 2^64; `& MASK64` marks the spots where the C code wraps.
19
+ module XXH3
20
+ # Constant and helper names keep upstream's `xxhash.h` identifiers (PRIME64_1, accumulate_512,
21
+ # …) so the port stays diffable against the reference scalar path.
22
+ # rubocop:disable Naming/VariableNumber
23
+ MASK64 = (1 << 64) - 1
24
+ MASK32 = (1 << 32) - 1
25
+
26
+ PRIME32_1 = 0x9E3779B1
27
+ PRIME32_2 = 0x85EBCA77
28
+ PRIME32_3 = 0xC2B2AE3D
29
+ PRIME64_1 = 0x9E3779B185EBCA87
30
+ PRIME64_2 = 0xC2B2AE3D27D4EB4F
31
+ PRIME64_3 = 0x165667B19E3779F9
32
+ PRIME64_4 = 0x85EBCA77C2B2AE63
33
+ PRIME64_5 = 0x27D4EB2F165667C5
34
+ PRIME_MX1 = 0x165667919E3779F9
35
+ PRIME_MX2 = 0x9FB21C651E98DF25
36
+
37
+ STRIPE_LEN = 64
38
+ SECRET_CONSUME_RATE = 8
39
+ ACC_NB = STRIPE_LEN / 8
40
+ MIDSIZE_MAX = 240
41
+ SECRET_SIZE_MIN = 136
42
+ MIDSIZE_STARTOFFSET = 3
43
+ MIDSIZE_LASTOFFSET = 17
44
+ SECRET_LASTACC_START = 7
45
+ SECRET_MERGEACCS_START = 11
46
+
47
+ # `XXH3_kSecret` — the 192-byte default secret taken from FARSH, verbatim from `xxhash.h`
48
+ # (one hex pair per upstream byte literal).
49
+ SECRET = [
50
+ "b8fe6c3923a44bbe7c01812cf721ad1cded46de9839097db7240a4a4b7b3671f" \
51
+ "cb79e64eccc0e578825ad07dccff7221b8084674f743248ee03590e6813a264c" \
52
+ "3c2852bb91c300cb88d0658b1b532ea371644897a20df94e3819ef46a9deacd8" \
53
+ "a8fa763fe39c343ff9dcbbc7c70b4f1d8a51e04bcdb45931c89f7ec9d9787364" \
54
+ "eac5ac8334d3ebc3c581a0fffa1363eb170ddd51b7f0da49d316552629d4689e" \
55
+ "2b16be587d47a1fc8ff8b8d17ad031ce45cb3a8f95160428afd7fbcabb4b407e"
56
+ ].pack("H*").freeze
57
+
58
+ # XXH3_INIT_ACC
59
+ INIT_ACC = [PRIME32_3, PRIME64_1, PRIME64_2, PRIME64_3, PRIME64_4, PRIME32_2, PRIME64_5, PRIME32_1].freeze
60
+
61
+ private_constant :MASK64, :MASK32, :PRIME32_1, :PRIME32_2, :PRIME32_3, :PRIME64_1, :PRIME64_2, :PRIME64_3,
62
+ :PRIME64_4, :PRIME64_5, :PRIME_MX1, :PRIME_MX2, :STRIPE_LEN, :SECRET_CONSUME_RATE,
63
+ :ACC_NB, :MIDSIZE_MAX, :SECRET_SIZE_MIN, :MIDSIZE_STARTOFFSET, :MIDSIZE_LASTOFFSET,
64
+ :SECRET_LASTACC_START, :SECRET_MERGEACCS_START, :SECRET, :INIT_ACC
65
+
66
+ class << self
67
+ # XXH3_64bits of `bytes`, hashed as raw bytes — the string's encoding is ignored.
68
+ def digest64(bytes)
69
+ len = bytes.bytesize
70
+ if len <= 16 then len_0to16(bytes, len)
71
+ elsif len <= 128 then len_17to128(bytes, len)
72
+ elsif len <= MIDSIZE_MAX then len_129to240(bytes, len)
73
+ else hash_long(bytes, len)
74
+ end
75
+ end
76
+
77
+ # lisplens ADR-0008's `anchor_hash`: the low 16 bits of the digest as 4 lowercase hex digits.
78
+ def anchor_hash(bytes)
79
+ format("%04x", digest64(bytes) & 0xffff)
80
+ end
81
+
82
+ # lisplens ADR-0008's `file_hash`: the full digest as 16 lowercase hex digits.
83
+ def file_hash(bytes)
84
+ format("%016x", digest64(bytes))
85
+ end
86
+
87
+ private
88
+
89
+ # --- short keys: len <= 16 ---
90
+
91
+ def len_0to16(bytes, len)
92
+ if len > 8 then len_9to16(bytes, len)
93
+ elsif len >= 4 then len_4to8(bytes, len)
94
+ elsif len.positive? then len_1to3(bytes, len)
95
+ else
96
+ xxh64_avalanche(u64(SECRET, 56) ^ u64(SECRET, 64))
97
+ end
98
+ end
99
+
100
+ def len_1to3(bytes, len)
101
+ c1 = bytes.getbyte(0)
102
+ c2 = bytes.getbyte(len >> 1)
103
+ c3 = bytes.getbyte(len - 1)
104
+ combined = (c1 << 16) | (c2 << 24) | c3 | (len << 8)
105
+ bitflip = u32(SECRET, 0) ^ u32(SECRET, 4)
106
+ xxh64_avalanche(combined ^ bitflip)
107
+ end
108
+
109
+ def len_4to8(bytes, len)
110
+ input1 = u32(bytes, 0)
111
+ input2 = u32(bytes, len - 4)
112
+ bitflip = u64(SECRET, 8) ^ u64(SECRET, 16)
113
+ keyed = (input2 + (input1 << 32)) ^ bitflip
114
+ rrmxmx(keyed, len)
115
+ end
116
+
117
+ def len_9to16(bytes, len)
118
+ bitflip1 = u64(SECRET, 24) ^ u64(SECRET, 32)
119
+ bitflip2 = u64(SECRET, 40) ^ u64(SECRET, 48)
120
+ input_lo = u64(bytes, 0) ^ bitflip1
121
+ input_hi = u64(bytes, len - 8) ^ bitflip2
122
+ acc = (len + swap64(input_lo) + input_hi + mul128_fold64(input_lo, input_hi)) & MASK64
123
+ xxh3_avalanche(acc)
124
+ end
125
+
126
+ # --- mid-size keys: 17 <= len <= 240 (mum-hash on 16-byte chunks) ---
127
+
128
+ def mix16b(bytes, in_off, sec_off)
129
+ input_lo = u64(bytes, in_off)
130
+ input_hi = u64(bytes, in_off + 8)
131
+ mul128_fold64(input_lo ^ u64(SECRET, sec_off), input_hi ^ u64(SECRET, sec_off + 8))
132
+ end
133
+
134
+ def len_17to128(bytes, len)
135
+ acc = len * PRIME64_1
136
+ if len > 32
137
+ if len > 64
138
+ if len > 96
139
+ acc += mix16b(bytes, 48, 96)
140
+ acc += mix16b(bytes, len - 64, 112)
141
+ end
142
+ acc += mix16b(bytes, 32, 64)
143
+ acc += mix16b(bytes, len - 48, 80)
144
+ end
145
+ acc += mix16b(bytes, 16, 32)
146
+ acc += mix16b(bytes, len - 32, 48)
147
+ end
148
+ acc += mix16b(bytes, 0, 0)
149
+ acc += mix16b(bytes, len - 16, 16)
150
+ xxh3_avalanche(acc & MASK64)
151
+ end
152
+
153
+ def len_129to240(bytes, len)
154
+ acc = len * PRIME64_1
155
+ nb_rounds = len / 16
156
+ 8.times { |i| acc += mix16b(bytes, 16 * i, 16 * i) }
157
+ acc_end = mix16b(bytes, len - 16, SECRET_SIZE_MIN - MIDSIZE_LASTOFFSET)
158
+ acc = xxh3_avalanche(acc & MASK64)
159
+ (8...nb_rounds).each do |i|
160
+ acc_end += mix16b(bytes, 16 * i, (16 * (i - 8)) + MIDSIZE_STARTOFFSET)
161
+ end
162
+ xxh3_avalanche((acc + acc_end) & MASK64)
163
+ end
164
+
165
+ # --- long keys: len > 240 (striped accumulate + scramble, then merge) ---
166
+
167
+ def hash_long(bytes, len)
168
+ acc = INIT_ACC.dup
169
+ secret_size = SECRET.bytesize
170
+ nb_stripes_per_block = (secret_size - STRIPE_LEN) / SECRET_CONSUME_RATE
171
+ block_len = STRIPE_LEN * nb_stripes_per_block
172
+ nb_blocks = (len - 1) / block_len
173
+
174
+ nb_blocks.times do |n|
175
+ accumulate_stripes(acc, bytes, n * block_len, nb_stripes_per_block)
176
+ scramble(acc, secret_size - STRIPE_LEN)
177
+ end
178
+
179
+ last_off = nb_blocks * block_len
180
+ accumulate_stripes(acc, bytes, last_off, (len - 1 - last_off) / STRIPE_LEN)
181
+ accumulate_512(acc, bytes, len - STRIPE_LEN, secret_size - STRIPE_LEN - SECRET_LASTACC_START)
182
+
183
+ merge_accs(acc, len * PRIME64_1)
184
+ end
185
+
186
+ def accumulate_stripes(acc, bytes, base_off, nb_stripes)
187
+ nb_stripes.times do |s|
188
+ accumulate_512(acc, bytes, base_off + (s * STRIPE_LEN), s * SECRET_CONSUME_RATE)
189
+ end
190
+ end
191
+
192
+ # XXH3_accumulate_512_scalar: each 64-byte stripe folds into the 8-lane accumulator — the
193
+ # data value is added to the swapped adjacent lane, and the secret-keyed value contributes a
194
+ # 32x32->64 product to its own lane.
195
+ def accumulate_512(acc, bytes, in_off, sec_off)
196
+ ACC_NB.times do |i|
197
+ data_val = u64(bytes, in_off + (8 * i))
198
+ data_key = data_val ^ u64(SECRET, sec_off + (8 * i))
199
+ acc[i ^ 1] = (acc[i ^ 1] + data_val) & MASK64
200
+ acc[i] = (acc[i] + ((data_key & MASK32) * (data_key >> 32))) & MASK64
201
+ end
202
+ end
203
+
204
+ # XXH3_scrambleAcc_scalar
205
+ def scramble(acc, sec_off)
206
+ ACC_NB.times do |i|
207
+ acc[i] = ((acc[i] ^ (acc[i] >> 47) ^ u64(SECRET, sec_off + (8 * i))) * PRIME32_1) & MASK64
208
+ end
209
+ end
210
+
211
+ # XXH3_mergeAccs over the default secret, at SECRET_MERGEACCS_START.
212
+ def merge_accs(acc, start)
213
+ result = start
214
+ 4.times do |i|
215
+ result += mul128_fold64(acc[2 * i] ^ u64(SECRET, SECRET_MERGEACCS_START + (16 * i)),
216
+ acc[(2 * i) + 1] ^ u64(SECRET, SECRET_MERGEACCS_START + (16 * i) + 8))
217
+ end
218
+ xxh3_avalanche(result & MASK64)
219
+ end
220
+
221
+ # --- primitives ---
222
+
223
+ def u32(bytes, off) = bytes.unpack1("L<", offset: off)
224
+
225
+ def u64(bytes, off) = bytes.unpack1("Q<", offset: off)
226
+
227
+ def swap64(value) = [value].pack("Q>").unpack1("Q<")
228
+
229
+ def rotl64(value, shift) = ((value << shift) | (value >> (64 - shift))) & MASK64
230
+
231
+ def mul128_fold64(lhs, rhs)
232
+ product = lhs * rhs
233
+ (product & MASK64) ^ (product >> 64)
234
+ end
235
+
236
+ # XXH3_avalanche — the fast finalizer used when the input is already partially mixed.
237
+ def xxh3_avalanche(hash)
238
+ hash = (hash ^ (hash >> 37)) & MASK64
239
+ hash = (hash * PRIME_MX1) & MASK64
240
+ hash ^ (hash >> 32)
241
+ end
242
+
243
+ # XXH64_avalanche — the stronger finalizer for the shortest keys.
244
+ def xxh64_avalanche(hash)
245
+ hash ^= hash >> 33
246
+ hash = (hash * PRIME64_2) & MASK64
247
+ hash ^= hash >> 29
248
+ hash = (hash * PRIME64_3) & MASK64
249
+ hash ^ (hash >> 32)
250
+ end
251
+
252
+ # XXH3_rrmxmx — the len 4-8 finalizer (Pelle Evensen's mix).
253
+ def rrmxmx(hash, len)
254
+ hash = (hash ^ rotl64(hash, 49) ^ rotl64(hash, 24)) & MASK64
255
+ hash = (hash * PRIME_MX2) & MASK64
256
+ hash ^= (hash >> 35) + len
257
+ hash = (hash * PRIME_MX2) & MASK64
258
+ hash ^ (hash >> 28)
259
+ end
260
+ end
261
+ # rubocop:enable Naming/VariableNumber
262
+ end
263
+ end
264
+ end