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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/manual/02-cli-reference.md +63 -2
- data/docs/manual/03-configuration.md +7 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +520 -49
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_session.rb +36 -4
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +20 -11
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
- data/lib/rigor/analysis/runner.rb +278 -20
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +53 -10
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/incremental_snapshot.rb +33 -1
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +0 -1
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/configuration/severity_profile.rb +19 -3
- data/lib/rigor/configuration.rb +66 -3
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +43 -31
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/lockfile_resolver.rb +17 -0
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_loader.rb +399 -133
- data/lib/rigor/environment.rb +103 -38
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +73 -27
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +387 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1532 -496
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +167 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/multi_target_binder.rb +340 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +540 -120
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +4615 -551
- data/lib/rigor/inference/statement_evaluator.rb +3176 -490
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +168 -5
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +3 -1
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +1 -2
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +19 -6
- data/lib/rigor/reflection.rb +72 -105
- data/lib/rigor/scope/discovery_index.rb +135 -2
- data/lib/rigor/scope.rb +859 -49
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +558 -31
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +88 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +12 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +1 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +135 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +9 -7
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +33 -8
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +108 -1
data/lib/rigor/environment.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
362
|
-
#
|
|
363
|
-
#
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
#
|
|
367
|
-
|
|
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-
|
|
569
|
-
#
|
|
570
|
-
#
|
|
571
|
-
#
|
|
572
|
-
#
|
|
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
|
|
575
|
-
#
|
|
576
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
reporter
|
|
585
|
-
|
|
586
|
-
|
|
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
|