rigortype 0.3.9 → 0.4.1
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 +5 -4
- 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/08-understanding-errors.md +3 -2
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +65 -4
- data/docs/manual/03-configuration.md +12 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- 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 +427 -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 +638 -136
- data/lib/rigor/analysis/dependency_recorder.rb +222 -17
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
- data/lib/rigor/analysis/incremental_session.rb +309 -53
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- 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 +3 -1
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +46 -18
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
- data/lib/rigor/analysis/runner.rb +603 -54
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +93 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +409 -0
- data/lib/rigor/analysis/worker_session.rb +57 -13
- 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 +45 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +102 -8
- 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 +63 -4
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/check_command.rb +83 -13
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- 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/unused_command.rb +3 -3
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +24 -3
- data/lib/rigor/configuration.rb +71 -7
- 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 +49 -37
- 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/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/lockfile_resolver.rb +27 -1
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_loader.rb +400 -134
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +104 -96
- 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 +88 -26
- 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 +393 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +3 -2
- 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/definer_resolution.rb +266 -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 +1665 -538
- data/lib/rigor/inference/external_ancestor_resolution.rb +264 -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 +488 -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 +179 -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 +901 -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/module_function_state.rb +213 -0
- data/lib/rigor/inference/multi_target_binder.rb +345 -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 +674 -124
- 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 +13 -11
- 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/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +5913 -701
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +3249 -497
- 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 +196 -13
- 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/io_boundary.rb +82 -4
- data/lib/rigor/plugin/isolation.rb +1 -1
- 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/trust_policy.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +20 -9
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +4 -5
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +146 -0
- data/lib/rigor/reflection/constant_path.rb +41 -13
- data/lib/rigor/reflection.rb +73 -107
- data/lib/rigor/scope/discovery_index.rb +351 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +884 -0
- data/lib/rigor/scope.rb +981 -198
- 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 +580 -47
- 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 +112 -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-ffi/lib/rigor/plugin/ffi.rb +10 -0
- 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 +14 -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 +2 -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 +177 -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 +46 -18
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +40 -10
- 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 +132 -1
|
@@ -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
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../type"
|
|
4
4
|
require_relative "range_constant"
|
|
5
|
+
require_relative "content_join"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Inference
|
|
@@ -74,10 +75,64 @@ module Rigor
|
|
|
74
75
|
}.freeze
|
|
75
76
|
private_constant :TYPE_HANDLERS
|
|
76
77
|
|
|
78
|
+
UNREAD_HASH_REFUSED_KEY = :__rigor_acceptance_unread_hash_refused__
|
|
79
|
+
private_constant :UNREAD_HASH_REFUSED_KEY
|
|
80
|
+
|
|
81
|
+
# Whether a `maybe` for `param` against `arg` rests on a record that could not read a hash's entries: a
|
|
82
|
+
# `Hash` with a gradual arm ({#gradual_hash?}) or an open `HashShape`. The record cannot prove the key set
|
|
83
|
+
# of either, and its `maybe` climbs through whatever holds the record. `OverloadSelector`'s strict pass
|
|
84
|
+
# reads that `maybe` as no evidence for the overload, so this re-asks with each such record answering
|
|
85
|
+
# `no` and reports whether the pair then fails. A `maybe` from anywhere else (a subclass only the Ruby
|
|
86
|
+
# source declares, a splat under a parameter that is no record) survives the re-ask and still counts.
|
|
87
|
+
def maybe_rests_on_unread_hash?(param, arg)
|
|
88
|
+
return false unless contains_type?(param) { |type| type.is_a?(Type::HashShape) }
|
|
89
|
+
return false unless contains_type?(arg) { |type| unread_hash?(type) }
|
|
90
|
+
|
|
91
|
+
refusing_unread_hashes { accepts(param, arg, mode: :gradual).no? }
|
|
92
|
+
end
|
|
93
|
+
|
|
77
94
|
# rubocop:disable-next Metrics/ClassLength
|
|
78
95
|
class << self
|
|
79
96
|
private
|
|
80
97
|
|
|
98
|
+
# The flag lives on the thread, not on a parameter, because the record may sit any number of `accepts`
|
|
99
|
+
# calls below the one asked (a tuple element, a `Hash` value, a union member), and those calls go
|
|
100
|
+
# through each carrier's `Type#accepts`. It is set only for the duration of the block.
|
|
101
|
+
def refusing_unread_hashes
|
|
102
|
+
previous = Thread.current[UNREAD_HASH_REFUSED_KEY]
|
|
103
|
+
Thread.current[UNREAD_HASH_REFUSED_KEY] = true
|
|
104
|
+
yield
|
|
105
|
+
ensure
|
|
106
|
+
Thread.current[UNREAD_HASH_REFUSED_KEY] = previous
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The `maybe` a record answers for a hash whose entries it cannot read, or `no` inside
|
|
110
|
+
# {#refusing_unread_hashes}.
|
|
111
|
+
def unread_hash_maybe(mode, reason)
|
|
112
|
+
return hash_shape_no(mode, "#{reason} (refused as evidence)") if Thread.current[UNREAD_HASH_REFUSED_KEY]
|
|
113
|
+
|
|
114
|
+
Type::AcceptsResult.maybe(mode: mode, reasons: reason)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def unread_hash?(type)
|
|
118
|
+
gradual_hash?(type) || (type.is_a?(Type::HashShape) && type.open?)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def contains_type?(type, &)
|
|
122
|
+
return true if yield(type)
|
|
123
|
+
|
|
124
|
+
components =
|
|
125
|
+
case type
|
|
126
|
+
when Type::Union then type.members
|
|
127
|
+
when Type::Nominal then type.type_args
|
|
128
|
+
when Type::Tuple then type.elements
|
|
129
|
+
when Type::HashShape then type.pairs.values
|
|
130
|
+
when Type::Difference then [type.base]
|
|
131
|
+
else return false
|
|
132
|
+
end
|
|
133
|
+
components.any? { |component| contains_type?(component, &) }
|
|
134
|
+
end
|
|
135
|
+
|
|
81
136
|
def accepts_one(self_type, other_type, mode)
|
|
82
137
|
handler = TYPE_HANDLERS[self_type.class]
|
|
83
138
|
return send(handler, self_type, other_type, mode) if handler
|
|
@@ -755,10 +810,15 @@ module Rigor
|
|
|
755
810
|
# `Difference[base, removed]` accepts another type X when the base accepts X *and* X's value set is
|
|
756
811
|
# provably disjoint from `removed`. The disjointness test is the subtle part — it is NOT the same as
|
|
757
812
|
# `removed.accepts(X)`, because `Nominal[String]` includes `""` even though `Constant[""]` does not
|
|
758
|
-
# "accept" `Nominal[String]`. The conservative rule here: we can prove disjointness only when X is
|
|
759
|
-
#
|
|
760
|
-
# removed value (already exhibits the disjointness)
|
|
761
|
-
#
|
|
813
|
+
# "accept" `Nominal[String]`. The conservative rule here: we can prove disjointness only when X is a
|
|
814
|
+
# carrier whose value set is visibly apart from `removed` — a `Constant` (compare values directly),
|
|
815
|
+
# another `Difference` with the same removed value (already exhibits the disjointness), an
|
|
816
|
+
# `IntegerRange` that skips an Integer removed value, a `Tuple` whose fixed arity differs from a
|
|
817
|
+
# removed `Tuple`, or a `HashShape` with a required key a closed removed `HashShape` cannot hold. The
|
|
818
|
+
# last three cover the empty witnesses of `non-zero-int`, `non-empty-array[T]` and
|
|
819
|
+
# `non-empty-hash[K, V]`. Any other carrier — a Nominal above all — is no proof and the difference
|
|
820
|
+
# rejects it under gradual mode, even where its values happen to exclude `removed`. A Union or
|
|
821
|
+
# Intersection argument never reaches this rule whole: `accepts` splits it into members first.
|
|
762
822
|
def accepts_difference(self_type, other_type, mode)
|
|
763
823
|
base_result = accepts(self_type.base, other_type, mode: mode)
|
|
764
824
|
return base_result if base_result.no?
|
|
@@ -787,9 +847,27 @@ module Rigor
|
|
|
787
847
|
# Disjointness is monotonic over Intersection: if any member is provably disjoint from
|
|
788
848
|
# `removed`, the meet is too.
|
|
789
849
|
other_type.members.any? { |m| provably_disjoint_from_removed?(m, removed) }
|
|
850
|
+
when Type::IntegerRange
|
|
851
|
+
removed.is_a?(Type::Constant) && removed.value.is_a?(Integer) && !other_type.covers?(removed.value)
|
|
852
|
+
when Type::Tuple
|
|
853
|
+
# A Tuple's arity is fixed, so it shares no inhabitant with a Tuple of another arity — the
|
|
854
|
+
# zero-arity witness `non-empty-array[T]` removes included.
|
|
855
|
+
removed.is_a?(Type::Tuple) && removed.elements.size != other_type.elements.size
|
|
856
|
+
when Type::HashShape
|
|
857
|
+
hash_shape_excludes_removed?(other_type, removed)
|
|
790
858
|
end
|
|
791
859
|
end
|
|
792
860
|
|
|
861
|
+
# Every inhabitant of `shape` holds each of its required keys, and a closed `removed` shape holds no
|
|
862
|
+
# key outside its pairs, so one required key `removed` does not list makes the two disjoint. Against
|
|
863
|
+
# the closed `{}` witness `non-empty-hash[K, V]` removes, that is any required key; an optional key
|
|
864
|
+
# proves nothing, since the shape is inhabited without it.
|
|
865
|
+
def hash_shape_excludes_removed?(shape, removed)
|
|
866
|
+
return false unless removed.is_a?(Type::HashShape) && removed.closed?
|
|
867
|
+
|
|
868
|
+
shape.required_keys.any? { |key| !removed.pairs.key?(key) }
|
|
869
|
+
end
|
|
870
|
+
|
|
793
871
|
# `Refined[base, predicate]` accepts another type X when the base accepts the *base* of X *and* X is
|
|
794
872
|
# provably contained in the predicate's value set. The base check is delegated to
|
|
795
873
|
# `accepts(self.base, X.base)` so handlers like `accepts_nominal` see Nominal-vs-Nominal and return
|
|
@@ -924,11 +1002,15 @@ module Rigor
|
|
|
924
1002
|
|
|
925
1003
|
# HashShape{k1: T1, ...} accepts another HashShape when every required key of self is required on
|
|
926
1004
|
# the other side and Ti accepts Ui (depth covariant). Optional keys may be absent on the other side;
|
|
927
|
-
# when present, their values are checked. A closed self rejects known
|
|
928
|
-
#
|
|
1005
|
+
# when present, their values are checked. A closed self rejects known extra keys, and answers at most
|
|
1006
|
+
# `maybe` for an open source: `h = { a: 1 }; h.default = 0` reopens a shape whose key set is still
|
|
1007
|
+
# exactly the literal's. Other types are rejected, except a `Hash` with a gradual arm (see
|
|
1008
|
+
# {#gradual_hash?}); the converse direction (a Nominal accepting a HashShape) is handled by
|
|
929
1009
|
# `accepts_nominal` via projection.
|
|
930
1010
|
def accepts_hash_shape(self_type, other_type, mode)
|
|
931
1011
|
unless other_type.is_a?(Type::HashShape)
|
|
1012
|
+
return gradual_hash_verdict(self_type, other_type, mode) if gradual_hash?(other_type)
|
|
1013
|
+
|
|
932
1014
|
return Type::AcceptsResult.no(
|
|
933
1015
|
mode: mode,
|
|
934
1016
|
reasons: "HashShape does not accept #{other_type.class}"
|
|
@@ -939,8 +1021,6 @@ module Rigor
|
|
|
939
1021
|
return hash_shape_no(mode, "HashShape missing required keys: #{missing.inspect}") unless missing.empty?
|
|
940
1022
|
|
|
941
1023
|
if self_type.closed?
|
|
942
|
-
return hash_shape_no(mode, "HashShape closed target rejects open source") if other_type.open?
|
|
943
|
-
|
|
944
1024
|
extra = other_type.pairs.keys - self_type.pairs.keys
|
|
945
1025
|
unless extra.empty?
|
|
946
1026
|
return hash_shape_no(mode, "HashShape closed target rejects extra keys: #{extra.inspect}")
|
|
@@ -948,9 +1028,59 @@ module Rigor
|
|
|
948
1028
|
end
|
|
949
1029
|
|
|
950
1030
|
per_entry = hash_shape_entry_results(self_type, other_type, mode)
|
|
951
|
-
combine_arg_results(per_entry, mode)
|
|
1031
|
+
result = combine_arg_results(per_entry, mode)
|
|
1032
|
+
return result unless result.yes? && self_type.closed? && other_type.open?
|
|
1033
|
+
|
|
1034
|
+
unread_hash_maybe(mode, "HashShape closed target cannot check open source")
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
# A `Hash` nominal — directly or as the base of a `Difference` (`non-empty-hash[K, V]`) — that is raw or
|
|
1038
|
+
# carries a `Dynamic` arm in a type argument. A hash literal with a `**splat` entry carries the arm by
|
|
1039
|
+
# design, `Hash.new` filled key by key reads as the raw `Hash`, and a declared `Hash[Symbol, untyped]`
|
|
1040
|
+
# is the author saying the hash may hold anything (the reading `MutationRejoin#regrowable_carrier?`
|
|
1041
|
+
# gives a declared gradual arm). The runtime value may then hold exactly the record's keys, so a shape
|
|
1042
|
+
# cannot reject it on the arm's account, the way `Hash[K, V]` accepts a raw `Hash` as maybe. A `Hash`
|
|
1043
|
+
# subclass is not read: its entries need not be the record's (`HashWithIndifferentAccess` stores String
|
|
1044
|
+
# keys).
|
|
1045
|
+
def gradual_hash?(type)
|
|
1046
|
+
nominal = type.is_a?(Type::Difference) ? type.base : type
|
|
1047
|
+
nominal.is_a?(Type::Nominal) && nominal.class_name == "Hash" &&
|
|
1048
|
+
(nominal.type_args.empty? || nominal.type_args.any? { |arg| gradual_arm?(arg) })
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
# `maybe`, unless an argument without a gradual arm already rules the record out: a precise `K` that
|
|
1052
|
+
# rejects a required key (`Hash[String, untyped]` never holds `:a`), or a precise `V` none of whose
|
|
1053
|
+
# members a required key's type accepts.
|
|
1054
|
+
def gradual_hash_verdict(self_type, other_type, mode)
|
|
1055
|
+
nominal = other_type.is_a?(Type::Difference) ? other_type.base : other_type
|
|
1056
|
+
key_type, value_type = nominal.type_args
|
|
1057
|
+
missing = unholdable_keys(self_type, key_type, mode)
|
|
1058
|
+
return hash_shape_no(mode, "Hash key type cannot hold required keys: #{missing.inspect}") if missing.any?
|
|
1059
|
+
|
|
1060
|
+
refused = refused_value_keys(self_type, value_type, mode)
|
|
1061
|
+
return hash_shape_no(mode, "Hash value type refused for required keys: #{refused.inspect}") if refused.any?
|
|
1062
|
+
|
|
1063
|
+
unread_hash_maybe(mode, "HashShape cannot check #{other_type.describe(:short)}")
|
|
952
1064
|
end
|
|
953
1065
|
|
|
1066
|
+
def unholdable_keys(self_type, key_type, mode)
|
|
1067
|
+
return [] if key_type.nil? || gradual_arm?(key_type)
|
|
1068
|
+
|
|
1069
|
+
self_type.required_keys.select { |key| key_type.accepts(Type::Combinator.constant_of(key), mode: mode).no? }
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
def refused_value_keys(self_type, value_type, mode)
|
|
1073
|
+
return [] if value_type.nil? || gradual_arm?(value_type)
|
|
1074
|
+
|
|
1075
|
+
members = ContentJoin.union_members(value_type)
|
|
1076
|
+
self_type.required_keys.select do |key|
|
|
1077
|
+
formal = self_type.pairs.fetch(key)
|
|
1078
|
+
members.all? { |member| accepts(formal, member, mode: mode).no? }
|
|
1079
|
+
end
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
def gradual_arm?(type) = ContentJoin.union_members(type).any?(Type::Dynamic)
|
|
1083
|
+
|
|
954
1084
|
def hash_shape_entry_results(self_type, other_type, mode)
|
|
955
1085
|
self_type.pairs.filter_map do |key, formal|
|
|
956
1086
|
next unless other_type.pairs.key?(key)
|