rigortype 0.3.8 → 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/capability_roles/capability_roles.rbs +36 -0
- 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 +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- 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 +261 -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 +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- 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 +229 -1
- 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 +3 -3
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -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 +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- 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 +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +112 -14
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +29 -11
- 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/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- 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 +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- 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 +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- 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 +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- 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/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- 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/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +16 -7
- 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 +168 -21
- 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 +87 -33
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
- 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/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- 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 +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- 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 +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- 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 +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- 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 +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- 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/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -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 +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -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/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- 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 +324 -18
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -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/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- 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 +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- 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 +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -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 +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- 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 +25 -27
- 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 +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- 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-project-init/references/06-agent-contract.md +67 -0
- 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 +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -3,28 +3,64 @@
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
5
|
require_relative "../type"
|
|
6
|
+
require_relative "optimistic_origin"
|
|
7
|
+
require_relative "method_dispatcher/rbs_dispatch"
|
|
6
8
|
|
|
7
9
|
module Rigor
|
|
8
10
|
module Inference
|
|
9
11
|
# Slice 5 phase 2 sub-phase 2 destructuring binder.
|
|
10
12
|
#
|
|
11
|
-
# `Rigor::Inference::MultiTargetBinder` decomposes a tuple-shaped right-hand side type
|
|
13
|
+
# `Rigor::Inference::MultiTargetBinder` decomposes a tuple- or array-shaped right-hand side type
|
|
12
14
|
# against a Prism multi-target tree and produces a `name -> Rigor::Type` binding map. The
|
|
13
|
-
# binder is shared between
|
|
15
|
+
# binder is shared between four surfaces:
|
|
14
16
|
#
|
|
15
17
|
# 1. `Rigor::Inference::StatementEvaluator#eval_multi_write` for the statement-level `a, b =
|
|
16
18
|
# rhs` form (`Prism::MultiWriteNode`).
|
|
17
19
|
# 2. `Rigor::Inference::BlockParameterBinder` for nested destructuring inside block parameter
|
|
18
20
|
# lists (`Prism::MultiTargetNode` under `BlockParametersNode#requireds`).
|
|
21
|
+
# 3. `Rigor::Inference::StatementEvaluator#bind_for_index` for `for a, b in pairs`.
|
|
22
|
+
# 4. `Rigor::Inference::ScopeIndexer`'s class-ivar pre-pass, for the ivar targets of `@a, @b = rhs`
|
|
23
|
+
# (issue #1110), which records {Result#ivars} and drops the marks, as it does for `@x = xs.first`,
|
|
24
|
+
# with `soften_slots: false` (see {.bind_marked}).
|
|
25
|
+
# 5. `Rigor::Inference::StatementEvaluator`'s `case/in` pattern binding (issue #1122), through
|
|
26
|
+
# {.decompose_slots}: a pattern binds its names against the same carriers, minus the two
|
|
27
|
+
# statement-only properties that method documents.
|
|
19
28
|
#
|
|
20
29
|
# Both Prism nodes share the same `lefts` / `rest` (a `Prism::SplatNode`) / `rights` triple,
|
|
21
30
|
# so the binder treats them uniformly. The binder is pure: it MUST NOT mutate its inputs and
|
|
22
31
|
# MUST return a fresh `Hash` on every call.
|
|
23
32
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
33
|
+
# Per-carrier rule for the right-hand side:
|
|
34
|
+
#
|
|
35
|
+
# - `Type::Tuple` (known arity) decomposes element-wise; see {slot_type} for the softening.
|
|
36
|
+
# - `Nominal[Array, [T]]`, and a `Refined` / `Difference` over one (`non-empty-array[T]`
|
|
37
|
+
# after an `empty?` guard), binds every fixed front/back slot to `T` and a named rest to
|
|
38
|
+
# `Array[T]` (issue #1093). A short array pads the fixed slots with `nil` at runtime, so
|
|
39
|
+
# `T` is the same optimistic read `Array#[]` / `Array#first` get past core RBS's
|
|
40
|
+
# `%a{implicitly-returns-nil}`: the binder reports those names in {Result#optimistic}, and
|
|
41
|
+
# every caller that applies the bindings to a scope records them through
|
|
42
|
+
# `Scope#with_optimistic_local` ({Result#apply_to}) so the ADR-101 branch elision
|
|
43
|
+
# declines on them. A nested target under an optimistic slot inherits the mark (`nil`
|
|
44
|
+
# destructures to `nil` for every inner name); the rest never carries it.
|
|
45
|
+
# - A `Type::Union` distributes (issue #1094): each member decomposes on its own against the
|
|
46
|
+
# same target tree, and each name binds the join of its per-member types. A member that binds
|
|
47
|
+
# a name to `Dynamic[Top]` — every name, for a member no rule here decomposes — makes that
|
|
48
|
+
# name `Dynamic[Top]` for the whole union rather than dropping out of the join. A member that
|
|
49
|
+
# binds a name to bare `nil` while another binds a value drops out of that name's join, and
|
|
50
|
+
# the name is marked optimistic: the per-slot softening of {slot_type}, applied across
|
|
51
|
+
# members (see {join_member_bindings}). So `a, b = ints[1..]` (`Array[Integer] | nil`) binds
|
|
52
|
+
# `Integer` to each slot, marked, exactly as a short `ints` would. A name is also optimistic
|
|
53
|
+
# when any member marked it, the rule `Scope#join` applies to the same mark at a merge.
|
|
54
|
+
# - A value that provably has no implicit `to_ary` conversion
|
|
55
|
+
# ({MethodDispatcher::RbsDispatch.array_conversion_free?}: `Integer`, `String`, `Hash`,
|
|
56
|
+
# `nil`, ... and RBS-known classes whose ancestry neither defines `to_ary` nor overrides
|
|
57
|
+
# `method_missing` / `respond_to_missing?`) is what Ruby wraps as `[rhs]`, so it decomposes
|
|
58
|
+
# as that one-element `Tuple` (issue #1094): `a, b = 1` binds `1` and `nil`. The wrap is
|
|
59
|
+
# exact rather than a bet, so it adds no mark.
|
|
60
|
+
# - Everything else — raw `Array`, `Array[Dynamic[top]]`, `Dynamic[Array[T]]` (whose static
|
|
61
|
+
# facet must not surface as a bare `T`), a value that may convert (`SimpleDelegator`, a
|
|
62
|
+
# Ruby-source class, a module, `Object`), `Top`, `Bot` — collapses to `Dynamic[Top]` per
|
|
63
|
+
# slot.
|
|
28
64
|
#
|
|
29
65
|
# Targets the binder recognises:
|
|
30
66
|
#
|
|
@@ -37,67 +73,299 @@ module Rigor
|
|
|
37
73
|
# same observable semantics (binding a fresh local in the block-entry scope).
|
|
38
74
|
# - `Prism::MultiTargetNode` — recurses with the slot's type as the new right-hand side.
|
|
39
75
|
# - `Prism::SplatNode` (used for `rest`) — its `expression` MUST be a
|
|
40
|
-
# `Prism::LocalVariableTargetNode
|
|
41
|
-
#
|
|
76
|
+
# `Prism::LocalVariableTargetNode`, a `Prism::RequiredParameterNode`, a
|
|
77
|
+
# `Prism::InstanceVariableTargetNode` or a `Prism::IndexTargetNode` to be observable; an
|
|
78
|
+
# anonymous `*` splat or any other target is skipped.
|
|
79
|
+
# - `Prism::InstanceVariableTargetNode` (issue #1110), as a fixed slot, a rest
|
|
80
|
+
# (`*@rest`), or inside a nested target. It decomposes by the same carrier rules as a
|
|
81
|
+
# local and is reported apart, in {Result#ivars} / {Result#optimistic_ivars}, so a caller
|
|
82
|
+
# that threads only locals never sees it. The binder keys it internally by its `:@name`,
|
|
83
|
+
# which no local name can collide with.
|
|
84
|
+
# - `Prism::IndexTargetNode` (`h[:a], x = rhs`), in the same three positions. It binds no
|
|
85
|
+
# name: it stores its slot through `[]=` on its receiver. The binder reports the value it
|
|
86
|
+
# stores in {Result#index_targets}, keyed by the target node itself, so
|
|
87
|
+
# `StatementEvaluator#eval_multi_write` and `#bind_for_index` (`for h[:a], w in pairs`) can
|
|
88
|
+
# widen the receiver's literal shape with that value as content evidence, exactly as a plain
|
|
89
|
+
# `h[:a] = 1` does. It carries no optimistic mark: there is no binding for one to qualify.
|
|
90
|
+
# Its slot is softened as a local's is; `eval_multi_write` documents why that stays honest
|
|
91
|
+
# without the mark.
|
|
42
92
|
#
|
|
43
|
-
# Other target kinds (`
|
|
44
|
-
# `
|
|
45
|
-
# MUST be silently skipped: they have no observable contribution to the
|
|
46
|
-
#
|
|
93
|
+
# Other target kinds (`ClassVariableTargetNode`, `GlobalVariableTargetNode`,
|
|
94
|
+
# `ConstantTargetNode`, `CallTargetNode`, `ConstantPathTargetNode`, `ImplicitRestNode`, ...)
|
|
95
|
+
# MUST be silently skipped: they have no observable contribution to the scope the
|
|
96
|
+
# StatementEvaluator threads.
|
|
47
97
|
#
|
|
48
98
|
# See docs/internal-spec/inference-engine.md for the binding contract and
|
|
49
99
|
# docs/adr/4-type-inference-engine.md for the slice rationale.
|
|
50
100
|
module MultiTargetBinder
|
|
101
|
+
NO_BINDINGS = {}.freeze
|
|
102
|
+
NO_NAMES = [].freeze
|
|
103
|
+
private_constant :NO_BINDINGS, :NO_NAMES
|
|
104
|
+
|
|
105
|
+
# `types` is the local `name -> Rigor::Type` map {.bind} returns; `optimistic` the frozen list
|
|
106
|
+
# of local names whose nil-freeness is the short-array bet described in the module comment.
|
|
107
|
+
# `ivars` / `optimistic_ivars` are the same pair for instance-variable targets.
|
|
108
|
+
# `index_targets` maps each `Prism::IndexTargetNode` to the value it stores; {#apply_to}
|
|
109
|
+
# binds nothing for it.
|
|
110
|
+
Result = Data.define(:types, :optimistic, :ivars, :optimistic_ivars, :index_targets) do
|
|
111
|
+
def initialize(types:, optimistic:, ivars: NO_BINDINGS, optimistic_ivars: NO_NAMES,
|
|
112
|
+
index_targets: NO_BINDINGS)
|
|
113
|
+
super
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Binds every name into `scope` and records the optimistic mark after the binding, since
|
|
117
|
+
# `Scope#with_local` / `Scope#with_ivar` drop any mark the name carried before. `miss` is what
|
|
118
|
+
# each marked slot answers on a miss (issue #1302): `nil` for every mark the binder makes itself —
|
|
119
|
+
# a short array pads with `nil`, and a `nil` destructures to `nil` — so only a caller whose
|
|
120
|
+
# right-hand side a miss can make something else passes another answer
|
|
121
|
+
# ({OptimisticOrigin.destructuring_miss}).
|
|
122
|
+
def apply_to(scope, miss: nil)
|
|
123
|
+
bound = types.reduce(scope) { |acc, (name, type)| acc.with_local(name, type) }
|
|
124
|
+
bound = ivars.reduce(bound) { |acc, (name, type)| acc.with_ivar(name, type) }
|
|
125
|
+
bound = optimistic.reduce(bound) do |acc, name|
|
|
126
|
+
acc.with_optimistic_local(name, OptimisticOrigin::IMPLICITLY_RETURNS_NIL, miss: miss)
|
|
127
|
+
end
|
|
128
|
+
optimistic_ivars.reduce(bound) do |acc, name|
|
|
129
|
+
acc.with_optimistic_ivar(name, OptimisticOrigin::IMPLICITLY_RETURNS_NIL, miss: miss)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
51
134
|
module_function
|
|
52
135
|
|
|
53
|
-
# @param
|
|
54
|
-
# @param
|
|
55
|
-
#
|
|
56
|
-
def bind(target_node, rhs_type)
|
|
136
|
+
# @param rhs_type — type of the right-hand side
|
|
137
|
+
# @param scope — the scope the destructure is evaluated in, which answers the `to_ary`
|
|
138
|
+
# question for a class outside the core list; without one only the core list wraps.
|
|
139
|
+
def bind(target_node, rhs_type, scope: nil)
|
|
140
|
+
bind_marked(target_node, rhs_type, scope: scope).types
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @param optimistic — whether `rhs_type` itself is an optimistic slot of an enclosing
|
|
144
|
+
# decomposition (a block's `|(g, h)|` fed from an auto-splatted `Array[T]`) or an
|
|
145
|
+
# optimistically nil-free value (`k, v = xs.first`), in which case every name bound under it
|
|
146
|
+
# inherits the mark. It may instead be the per-element Array
|
|
147
|
+
# {OptimisticOrigin.destructuring_marks} builds for a literal right-hand side
|
|
148
|
+
# (`x, y = xs.first, 1`), which marks the names under each fixed slot by that slot's element.
|
|
149
|
+
# @param soften_slots — false keeps a present `X | nil` tuple slot and a union member's bare
|
|
150
|
+
# `nil` in the binding instead of applying the ADR-57 softening of {slot_type} /
|
|
151
|
+
# {join_member_bindings}. That softening is only honest together with the optimistic mark
|
|
152
|
+
# it records, so a consumer that drops the marks (the class-ivar seed, issue #1110) turns it
|
|
153
|
+
# off; the `Array[T]` bet is unaffected, matching what `@x = xs.first` records.
|
|
154
|
+
def bind_marked(target_node, rhs_type, optimistic: false, scope: nil, soften_slots: true)
|
|
57
155
|
bindings = {}
|
|
58
|
-
|
|
59
|
-
bindings
|
|
156
|
+
marked = []
|
|
157
|
+
visit(target_node, rhs_type, optimistic, bindings, marked, [scope, soften_slots])
|
|
158
|
+
split_result(bindings, marked)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# The per-slot types of a positional pattern (`in [i, s]`, `in [*pre, m, *post]`), sharing
|
|
162
|
+
# every carrier rule {.bind_marked} applies to a multi-write target except two statement-only
|
|
163
|
+
# properties (issue #1122):
|
|
164
|
+
#
|
|
165
|
+
# - the multi-assign `[rhs]` wrap is NOT applied. `a, b = 1` binds `a` to `1` because Ruby wraps
|
|
166
|
+
# a right-hand side with no implicit `to_ary`; an array pattern instead matches through the
|
|
167
|
+
# subject's `deconstruct` / `to_ary`, and a subject with neither raises
|
|
168
|
+
# `NoMatchingPatternError` — no body is reached, so binding `1` there would type dead code.
|
|
169
|
+
# - no optimistic mark is reported. The ADR-101 short-array bet exists because a SHORT
|
|
170
|
+
# right-hand side pads the fixed slots with `nil`; a pattern that matched has every fixed slot
|
|
171
|
+
# it named, so there is nothing to bet on and `Result#optimistic` has no counterpart here.
|
|
172
|
+
#
|
|
173
|
+
# A `Type::Union` is the caller's to distribute: a pattern matches SOME member, and only the
|
|
174
|
+
# caller can tell which members can match it at all. Returns `[fronts, rest_type, backs]`, with
|
|
175
|
+
# every carrier no rule decomposes answering `Dynamic[Top]` per slot — the floor.
|
|
176
|
+
def decompose_slots(rhs_type, front_count:, back_count:, rest_present:, scope: nil, soften_slots: true)
|
|
177
|
+
fronts, rest_type, backs, = decompose(
|
|
178
|
+
rhs_type, front_count, back_count, rest_present: rest_present,
|
|
179
|
+
context: [scope, soften_slots], wrap_single: false
|
|
180
|
+
)
|
|
181
|
+
[fronts, rest_type, backs]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# The `T` of an `Array[T]` carrier the binder may decompose, or nil. Declines raw `Array`
|
|
185
|
+
# and an untyped / top element, which bind `Dynamic[Top]` per slot as before, and every
|
|
186
|
+
# `Dynamic` wrapper: `Dynamic[Array[T]]` is gradual, and projecting its static facet would
|
|
187
|
+
# hand the slot a bare `T` that licenses the negative rules (ADR-5). Shared with
|
|
188
|
+
# {BlockParameterBinder}'s auto-splat so both surfaces accept the same carriers.
|
|
189
|
+
def array_element_type(type)
|
|
190
|
+
case type
|
|
191
|
+
when Type::Nominal
|
|
192
|
+
return nil unless type.class_name == "Array" && type.type_args.size == 1
|
|
193
|
+
|
|
194
|
+
element = type.type_args.first
|
|
195
|
+
element.is_a?(Type::Dynamic) || element.is_a?(Type::Top) ? nil : element
|
|
196
|
+
when Type::Refined, Type::Difference
|
|
197
|
+
array_element_type(type.base)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The class whose instances `type` describes, for the `to_ary` question, or nil when `type`
|
|
202
|
+
# names no single class: `Dynamic`, `Top`, `Singleton`, `Intersection` and the rest decline.
|
|
203
|
+
# A union never reaches here; the binder distributes it first.
|
|
204
|
+
def conversion_class_name(type)
|
|
205
|
+
case type
|
|
206
|
+
when Type::Constant then type.value.class.name
|
|
207
|
+
when Type::Nominal then type.class_name
|
|
208
|
+
when Type::HashShape then "Hash"
|
|
209
|
+
when Type::IntegerRange then "Integer"
|
|
210
|
+
when Type::Refined, Type::Difference then conversion_class_name(type.base)
|
|
211
|
+
end
|
|
60
212
|
end
|
|
61
213
|
|
|
62
214
|
class << self
|
|
63
215
|
private
|
|
64
216
|
|
|
65
|
-
|
|
217
|
+
# Partitions the walk's single map: an ivar name carries its `@` sigil, which a local name
|
|
218
|
+
# never does, and an index target is keyed by its node rather than by a name.
|
|
219
|
+
def split_result(bindings, marked)
|
|
220
|
+
return Result.new(types: bindings, optimistic: marked.freeze) if bindings.each_key.all? { |k| local_key?(k) }
|
|
221
|
+
|
|
222
|
+
grouped = bindings.group_by { |key, _| key_kind(key) }.transform_values(&:to_h)
|
|
223
|
+
ivar_marked, local_marked = marked.partition { |name| key_kind(name) == :ivar }
|
|
224
|
+
Result.new(types: grouped.fetch(:local, {}), optimistic: local_marked.freeze,
|
|
225
|
+
ivars: grouped.fetch(:ivar, NO_BINDINGS), optimistic_ivars: ivar_marked.freeze,
|
|
226
|
+
index_targets: grouped.fetch(:index, NO_BINDINGS))
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def local_key?(key) = key.is_a?(Symbol) && !key.start_with?("@")
|
|
230
|
+
|
|
231
|
+
def key_kind(key)
|
|
232
|
+
return :index unless key.is_a?(Symbol)
|
|
233
|
+
|
|
234
|
+
key.start_with?("@") ? :ivar : :local
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# `context` is the `[scope, soften_slots]` pair every step of the walk shares.
|
|
238
|
+
def visit(node, rhs_type, optimistic, bindings, marked, context)
|
|
239
|
+
if rhs_type.is_a?(Type::Union)
|
|
240
|
+
return visit_union(node, rhs_type.members, optimistic, bindings, marked, context)
|
|
241
|
+
end
|
|
242
|
+
|
|
66
243
|
lefts = node.lefts || []
|
|
67
244
|
rest = node.rest
|
|
68
245
|
rights = node.rights || []
|
|
246
|
+
rest_present = !rest.nil?
|
|
69
247
|
|
|
70
|
-
fronts, rest_type, backs
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
248
|
+
fronts, rest_type, backs, slots_optimistic =
|
|
249
|
+
decompose(rhs_type, lefts.size, rights.size, rest_present: rest_present, context: context)
|
|
250
|
+
rest_type = arity_free_rest(rest_type) if optimistic == true
|
|
251
|
+
front_marks, back_marks =
|
|
252
|
+
slot_marks(optimistic, slots_optimistic, rhs_type, [lefts.size, rights.size], rest_present: rest_present)
|
|
253
|
+
lefts.each_with_index { |t, i| bind_target(t, fronts[i], front_marks[i], bindings, marked, context) }
|
|
254
|
+
bind_rest_target(rest, rest_type, bindings, marked) if rest
|
|
255
|
+
rights.each_with_index { |t, i| bind_target(t, backs[i], back_marks[i], bindings, marked, context) }
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The `[front_marks, back_marks]` pair a visit hands its fixed slots. A boolean `optimistic`
|
|
259
|
+
# applies to every slot, as does the decomposition's own short-array bet (`fallback`, the
|
|
260
|
+
# issue #1093 `Array[T]` mark). A per-element Array applies only to the `Type::Tuple` a literal
|
|
261
|
+
# right-hand side types as, of the same arity, and each slot picks its element at the offset
|
|
262
|
+
# {decompose_tuple} reads ({slot_offsets}); any other carrier falls back to `fallback`, and a
|
|
263
|
+
# slot past the literal's end binds an exact `nil` that no mark qualifies.
|
|
264
|
+
def slot_marks(optimistic, fallback, rhs_type, (front_count, back_count), rest_present:)
|
|
265
|
+
literal = optimistic.is_a?(Array) && rhs_type.is_a?(Type::Tuple) && rhs_type.elements.size == optimistic.size
|
|
266
|
+
uniform = optimistic.is_a?(Array) ? fallback : optimistic || fallback
|
|
267
|
+
return [[uniform] * front_count, [uniform] * back_count] unless literal
|
|
268
|
+
|
|
269
|
+
slot_offsets(optimistic.size, front_count, back_count, rest_present: rest_present)
|
|
270
|
+
.map { |offsets| offsets.map { |i| optimistic.fetch(i, false) } }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Every member walks the same target tree, so each binds the same names; the first member's
|
|
274
|
+
# key order is the declaration order.
|
|
275
|
+
def visit_union(node, members, optimistic, bindings, marked, context)
|
|
276
|
+
walks = members.map do |member|
|
|
277
|
+
member_bindings = {}
|
|
278
|
+
member_marked = []
|
|
279
|
+
visit(node, member, optimistic, member_bindings, member_marked, context)
|
|
280
|
+
[member_bindings, member_marked]
|
|
281
|
+
end
|
|
282
|
+
walks.first.first.each_key do |name|
|
|
283
|
+
types = walks.map { |member_bindings, _| member_bindings[name] }
|
|
284
|
+
joined, softened = join_member_bindings(types, context.last)
|
|
285
|
+
mark = softened || walks.any? { |_, member_marked| member_marked.include?(name) }
|
|
286
|
+
bind_name(name, joined, mark, bindings, marked)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Returns `[type, softened]`. `Dynamic[Top]` from any member is the whole answer: the join
|
|
291
|
+
# must not let a decomposable member's precise type stand for a member nothing is known
|
|
292
|
+
# about. A member that binds the name to bare `nil` — a `nil` slot, a slot past a short
|
|
293
|
+
# member, or a `nil` member wrapped as `[nil]` — is left out of the join when another member
|
|
294
|
+
# binds a value, and `softened` reports it so the caller marks the name optimistic. This is
|
|
295
|
+
# {slot_type}'s ADR-57 softening across members: which member arrived is correlated with the
|
|
296
|
+
# other slots (`k, v = hash.find { ... }; v.x if k`, `status, value = ok ? [:ok, v] : [:err]`),
|
|
297
|
+
# and a per-slot `T | nil` fires `call.possible-nil-receiver` on the guarded read.
|
|
298
|
+
def join_member_bindings(types, soften_slots)
|
|
299
|
+
untyped = Type::Combinator.untyped
|
|
300
|
+
return [untyped, false] if types.any? { |type| type == untyped }
|
|
301
|
+
|
|
302
|
+
firm = types.reject { |type| nil_literal?(type) }
|
|
303
|
+
return [Type::Combinator.union(*types), false] if !soften_slots || firm.empty? || firm.size == types.size
|
|
304
|
+
|
|
305
|
+
[Type::Combinator.union(*firm), true]
|
|
74
306
|
end
|
|
75
307
|
|
|
76
308
|
# Decomposes the right-hand side type into the per-slot types. Returns a `[fronts,
|
|
77
|
-
# rest_type, backs]`
|
|
78
|
-
# `front_count`/`back_count`,
|
|
79
|
-
# `rest_present:` is true) or `nil
|
|
80
|
-
|
|
309
|
+
# rest_type, backs, optimistic]` quadruple, with `fronts` and `backs` each an ordered
|
|
310
|
+
# array of length `front_count`/`back_count`, `rest_type` either a `Rigor::Type` (when
|
|
311
|
+
# `rest_present:` is true) or `nil`, and `optimistic` true when the fixed slots are the
|
|
312
|
+
# short-array bet rather than known elements.
|
|
313
|
+
def decompose(rhs_type, front_count, back_count, rest_present:, context:, wrap_single: true)
|
|
314
|
+
scope, soften = context
|
|
81
315
|
if rhs_type.is_a?(Type::Tuple)
|
|
82
|
-
decompose_tuple(rhs_type, front_count, back_count, rest_present: rest_present)
|
|
316
|
+
[*decompose_tuple(rhs_type, front_count, back_count, rest_present: rest_present, soften: soften), false]
|
|
317
|
+
elsif (element = array_element_type(rhs_type))
|
|
318
|
+
[*decompose_array(element, front_count, back_count, rest_present: rest_present), true]
|
|
319
|
+
elsif wrap_single && wraps_as_single_element?(rhs_type, scope)
|
|
320
|
+
wrapped = Type::Combinator.tuple_of(rhs_type)
|
|
321
|
+
[*decompose_tuple(wrapped, front_count, back_count, rest_present: rest_present, soften: soften), false]
|
|
83
322
|
else
|
|
84
|
-
decompose_default(front_count, back_count, rest_present: rest_present)
|
|
323
|
+
[*decompose_default(front_count, back_count, rest_present: rest_present), false]
|
|
85
324
|
end
|
|
86
325
|
end
|
|
87
326
|
|
|
88
|
-
|
|
327
|
+
# Ruby's `[rhs]` wrap for a value with no implicit `to_ary`; see the module comment.
|
|
328
|
+
def wraps_as_single_element?(rhs_type, scope)
|
|
329
|
+
class_name = conversion_class_name(rhs_type)
|
|
330
|
+
!class_name.nil? && MethodDispatcher::RbsDispatch.array_conversion_free?(class_name, scope)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Under an inherited mark the whole right-hand side may be the `nil` a short outer array padded in, and
|
|
334
|
+
# `(p, *q) = nil` binds `q = []`. A Tuple rest would claim the middle elements are present
|
|
335
|
+
# (`q.first.nil?` folding to `false`), so it widens to `Array[union of the middle]`, which admits the
|
|
336
|
+
# empty array; an empty middle has no element type to offer and stays `Array[Dynamic[top]]`.
|
|
337
|
+
def arity_free_rest(rest_type)
|
|
338
|
+
return rest_type unless rest_type.is_a?(Type::Tuple)
|
|
339
|
+
|
|
340
|
+
element = rest_type.elements.empty? ? Type::Combinator.untyped : Type::Combinator.union(*rest_type.elements)
|
|
341
|
+
Type::Combinator.nominal_of("Array", type_args: [element])
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def decompose_array(element, front_count, back_count, rest_present:)
|
|
345
|
+
[
|
|
346
|
+
Array.new(front_count) { element },
|
|
347
|
+
rest_present ? Type::Combinator.nominal_of("Array", type_args: [element]) : nil,
|
|
348
|
+
Array.new(back_count) { element }
|
|
349
|
+
]
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def decompose_tuple(tuple, front_count, back_count, rest_present:, soften:)
|
|
89
353
|
elements = tuple.elements
|
|
90
|
-
fronts =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
354
|
+
fronts, backs = slot_offsets(elements.size, front_count, back_count, rest_present: rest_present)
|
|
355
|
+
.map { |offsets| offsets.map { |i| slot_type(elements, i, soften) } }
|
|
356
|
+
# The end is clamped at the fronts, as {slot_offsets} clamps the backs: an unclamped negative end
|
|
357
|
+
# (more back slots than elements) would count from the end instead. A range starting past the end
|
|
358
|
+
# slices to `nil`.
|
|
359
|
+
middle = elements[front_count...[elements.size - back_count, front_count].max] || []
|
|
360
|
+
[fronts, rest_present ? Type::Combinator.tuple_of(*middle) : nil, backs]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# The element offsets a `[fronts, backs]` pair of fixed slots reads out of `size` elements: the
|
|
364
|
+
# back slots follow the middle a rest absorbs, and follow the fronts directly without one. An
|
|
365
|
+
# offset past `size` is a slot the source is too short to fill.
|
|
366
|
+
def slot_offsets(size, front_count, back_count, rest_present:)
|
|
367
|
+
back_start = rest_present ? [size - back_count, front_count].max : front_count
|
|
368
|
+
[Array.new(front_count) { |i| i }, Array.new(back_count) { |i| back_start + i }]
|
|
101
369
|
end
|
|
102
370
|
|
|
103
371
|
# The per-slot type for index `i` of a tuple decomposition, FP-safely softened: a
|
|
@@ -118,11 +386,11 @@ module Rigor
|
|
|
118
386
|
# outranks the worst-case per-slot reading, so we drop the `nil` from a destructured slot
|
|
119
387
|
# and keep the non-`nil` constituent (a bare `nil` slot stays `nil` — there is nothing to
|
|
120
388
|
# soften). A pure non-optional element keeps its precise type unchanged.
|
|
121
|
-
def slot_type(elements, index)
|
|
389
|
+
def slot_type(elements, index, soften)
|
|
122
390
|
element = elements[index]
|
|
123
391
|
return Type::Combinator.constant_of(nil) if element.nil?
|
|
124
392
|
|
|
125
|
-
soften_optional_slot(element)
|
|
393
|
+
soften ? soften_optional_slot(element) : element
|
|
126
394
|
end
|
|
127
395
|
|
|
128
396
|
def soften_optional_slot(element)
|
|
@@ -147,24 +415,42 @@ module Rigor
|
|
|
147
415
|
]
|
|
148
416
|
end
|
|
149
417
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
418
|
+
# A per-element Array mark reaches a name only through a nested target: a name bound to a
|
|
419
|
+
# whole literal array holds an Array, which is never `nil`. Nor does any mark reach a name bound
|
|
420
|
+
# to an exact `nil` — a slot past a Tuple's end or a wrap's empty slot — which is `nil` whether
|
|
421
|
+
# or not the source missed, so the mark could only withhold an honest verdict.
|
|
422
|
+
def bind_target(target, type, optimistic, bindings, marked, context)
|
|
423
|
+
return visit(target, type, optimistic, bindings, marked, context) if target.is_a?(Prism::MultiTargetNode)
|
|
424
|
+
|
|
425
|
+
key = binding_key(target)
|
|
426
|
+
bind_name(key, type, optimistic == true && !nil_literal?(type), bindings, marked) if key
|
|
157
427
|
end
|
|
158
428
|
|
|
159
|
-
|
|
429
|
+
# The rest is an `Array` even when the right-hand side is short, so it is never marked.
|
|
430
|
+
def bind_rest_target(splat_node, type, bindings, marked)
|
|
160
431
|
return unless splat_node.is_a?(Prism::SplatNode)
|
|
161
432
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
433
|
+
key = binding_key(splat_node.expression)
|
|
434
|
+
bind_name(key, type, false, bindings, marked) if key
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# A named target's name, an index target's node (it binds no name, see {bind_name}), or nil for
|
|
438
|
+
# a target that contributes nothing to the scope.
|
|
439
|
+
def binding_key(target)
|
|
440
|
+
case target
|
|
441
|
+
when Prism::LocalVariableTargetNode, Prism::RequiredParameterNode, Prism::InstanceVariableTargetNode
|
|
442
|
+
target.name
|
|
443
|
+
when Prism::IndexTargetNode then target
|
|
166
444
|
end
|
|
167
445
|
end
|
|
446
|
+
|
|
447
|
+
# A later binding of the same name (`a, a = ints`) wins, mark included. `name` is the
|
|
448
|
+
# target node for an index target, which binds no name and so is never marked.
|
|
449
|
+
def bind_name(name, type, optimistic, bindings, marked)
|
|
450
|
+
bindings[name] = type
|
|
451
|
+
marked.delete(name)
|
|
452
|
+
marked << name if optimistic && name.is_a?(Symbol)
|
|
453
|
+
end
|
|
168
454
|
end
|
|
169
455
|
end
|
|
170
456
|
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
require_relative "content_join"
|
|
5
|
+
require_relative "rewrite_mutation"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
# The re-join half of the mutation widening (issue #580): what happens to the SECOND and later
|
|
10
|
+
# content mutations of one collection, once {MutationWidening} has already replaced the literal
|
|
11
|
+
# carrier with a `Nominal`.
|
|
12
|
+
#
|
|
13
|
+
# Split out of {MutationWidening} because it answers a different question. That module decides
|
|
14
|
+
# what a mutation FALSIFIES about a literal shape; this one decides when a carrier is still open
|
|
15
|
+
# enough to LEARN from, which is a provenance question the type system has no field for and this
|
|
16
|
+
# file answers off the carrier itself.
|
|
17
|
+
module MutationRejoin
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# Re-opens a carrier this seam already widened once, so the SECOND and later stores into the
|
|
21
|
+
# same collection are seen (issue #580).
|
|
22
|
+
#
|
|
23
|
+
# Before this arm the widening was a one-way door: the first content mutation replaced the
|
|
24
|
+
# literal `Tuple` / `HashShape` with a `Nominal`, `widen_for_mutator` had no `Nominal` arm, and
|
|
25
|
+
# `a = []; a.push(1); a.push("s")` closed at `Array[Integer | Dynamic[top]]` with the `String`
|
|
26
|
+
# arm simply absent. The `Dynamic` arm kept that from being a WRONG answer — nothing folds and
|
|
27
|
+
# nothing dispatches off a union carrying it — but the carrier still did not say what the
|
|
28
|
+
# program holds, and every precision lever that would eventually take the gradual arm off had
|
|
29
|
+
# to inherit the missing store.
|
|
30
|
+
#
|
|
31
|
+
# `nil` when the re-join changed nothing, so a non-adder (`pop`, `sort!`) on an already-widened
|
|
32
|
+
# carrier does not rebind the binding at all: a rebind drops the per-slot narrowings `with_local`
|
|
33
|
+
# keys to the old value, and paying that for an identical type is a straight loss.
|
|
34
|
+
def widen_nominal(nominal, method_name, values: :widen, arg_types: MutationWidening::NO_ARG_TYPES)
|
|
35
|
+
return nil unless regrowable_carrier?(nominal)
|
|
36
|
+
|
|
37
|
+
pre_state = values == :widen ? value_pin_widened_args(nominal) : nominal
|
|
38
|
+
rejoined =
|
|
39
|
+
case nominal.class_name
|
|
40
|
+
when "Array"
|
|
41
|
+
return nil unless MutationWidening::ARRAY_MUTATORS.include?(method_name)
|
|
42
|
+
|
|
43
|
+
MutationWidening.join_added_elements(pre_state, method_name, arg_types,
|
|
44
|
+
pinned_evidence(ContentJoin.collection_element_types(pre_state)))
|
|
45
|
+
when "Hash"
|
|
46
|
+
return nil unless MutationWidening::HASH_MUTATORS.include?(method_name)
|
|
47
|
+
|
|
48
|
+
seed_keys, seed_values = ContentJoin.hash_shape_key_values(pre_state)
|
|
49
|
+
MutationWidening.join_added_pairs(pre_state, method_name, arg_types,
|
|
50
|
+
[pinned_evidence(seed_keys), pinned_evidence(seed_values)])
|
|
51
|
+
end
|
|
52
|
+
# The arm lands after the precise parameters are restored: a rewriter falsifies a proven side too
|
|
53
|
+
# (`Hash.new(0)` under `transform_values!(&:to_s)` holds Strings), which an adder's one store cannot.
|
|
54
|
+
rejoined = RewriteMutation.arm(keep_precise_parameters(pre_state, rejoined), method_name)
|
|
55
|
+
rejoined == nominal ? nil : rejoined
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Re-grows only the parameters that were already gradual, restoring every other one.
|
|
59
|
+
#
|
|
60
|
+
# {#regrowable_carrier?} asks the carrier as a whole, but a `Hash` has two parameters and they
|
|
61
|
+
# can disagree: `Hash.new(0)` is `Hash[Dynamic[top], Integer]` — an open key side over a value
|
|
62
|
+
# side the default-arg fold PROVED. Re-joining both put the gradual floor on the value side and
|
|
63
|
+
# cost the counter idiom (`h = Hash.new(0); h[:x] += 1; h[:x]`) its `Integer` for a
|
|
64
|
+
# `Dynamic[top] | Integer`, a precision loss for evidence the key side alone was missing.
|
|
65
|
+
#
|
|
66
|
+
# The restored side comes from the pin-widened pre-state rather than the original carrier, so a
|
|
67
|
+
# slot-rewriting mutator still falsifies the pinning it falsifies.
|
|
68
|
+
def keep_precise_parameters(pre_state, rejoined)
|
|
69
|
+
return rejoined unless rejoined.is_a?(Type::Nominal) && rejoined.type_args.size == pre_state.type_args.size
|
|
70
|
+
|
|
71
|
+
args = pre_state.type_args.each_with_index.map do |arg, i|
|
|
72
|
+
gradual_parameter?(arg) ? rejoined.type_args[i] : arg
|
|
73
|
+
end
|
|
74
|
+
Type::Combinator.nominal_of(rejoined.class_name, type_args: args)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The class claim a re-opened carrier still makes, for {ContentJoin.admissible_evidence} to gate
|
|
78
|
+
# the added members against.
|
|
79
|
+
#
|
|
80
|
+
# It is the carrier's VALUE-PINNED arms and nothing else, because pinning is the only trace a
|
|
81
|
+
# literal seed's own elements leave once the carrier is nominal. Reading the whole element list
|
|
82
|
+
# instead gets both ends wrong. {ContentJoin.admissible_evidence} answers "everything is
|
|
83
|
+
# admissible" for a seed carrying a `Dynamic`, and a re-opened carrier ALWAYS carries one — its
|
|
84
|
+
# own gradual floor — so the gate would be off exactly where it is needed: haml's
|
|
85
|
+
# `temple = [:multi]` grew an `Array[…]` arm past its hand-written `-> Array[:multi]` and brought
|
|
86
|
+
# the #561 `def.return-type-mismatch` back. Gating on the full list is the opposite error — after
|
|
87
|
+
# `a = []; a.push(1)` the carrier reads `Array[Integer | Dynamic[top]]`, and a class set of
|
|
88
|
+
# `{Integer}` floors the `"s"` the next line stores, which is the whole answer this issue is
|
|
89
|
+
# about. The pinning tells the two apart: `:multi` is a claim the author's literal made, `Integer`
|
|
90
|
+
# is this seam's own join talking back to itself.
|
|
91
|
+
#
|
|
92
|
+
# No pinned arm means no claim to contradict — an empty-seeded accumulator — and the added
|
|
93
|
+
# evidence is admitted as itself.
|
|
94
|
+
def pinned_evidence(members)
|
|
95
|
+
members.flat_map { |m| ContentJoin.union_members(m) }.grep(Type::Constant)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Which nominals may re-grow — the FP boundary this whole issue turns on, read off the carrier
|
|
99
|
+
# itself rather than out of a side table.
|
|
100
|
+
#
|
|
101
|
+
# A carrier that already carries a gradual arm on the parameter the mutation feeds is one this
|
|
102
|
+
# seam (or a declaration) has already declared open. Growing it is free of the #561 blast
|
|
103
|
+
# radius by construction: adding a member to a union that already contains `Dynamic[top]`
|
|
104
|
+
# cannot make the union reject a declared type it accepted, cannot manufacture a constant fold
|
|
105
|
+
# (a union carrying `Dynamic` never folds), and cannot make a dispatch that was quiet fire.
|
|
106
|
+
# What it CAN do is stop dropping evidence — which is the whole point.
|
|
107
|
+
#
|
|
108
|
+
# A PRECISE nominal is refused, and that is exactly the case the issue names: haml's
|
|
109
|
+
# hand-written `-> Array[:multi]`, or an `Array[Symbol]` an RBS signature declares. Its element
|
|
110
|
+
# set is a claim somebody made about the collection, not a residue of this seam's own
|
|
111
|
+
# one-store guesswork, and joining a foreign member into it is what drew eight
|
|
112
|
+
# `def.return-type-mismatch` on correct code (PR #561). The seed-admissibility gate in
|
|
113
|
+
# {ContentJoin.admissible_evidence} stays the guard for everything that does get through.
|
|
114
|
+
#
|
|
115
|
+
# This is a superset of "mutation-widened": a DECLARED `Array[untyped]` or
|
|
116
|
+
# `Array[Integer | untyped]` matches too. Deliberate — the soundness argument above is about
|
|
117
|
+
# the carrier being open, not about who opened it, and a declared gradual arm is the author
|
|
118
|
+
# saying the collection may hold anything. Distinguishing the two needs provenance travelling
|
|
119
|
+
# WITH the value (a field on `Type::Nominal`, reopening structural equality, `Marshal`, and the
|
|
120
|
+
# cache schema); nothing on this axis needs it yet.
|
|
121
|
+
def regrowable_carrier?(nominal)
|
|
122
|
+
return false unless %w[Array Hash].include?(nominal.class_name)
|
|
123
|
+
|
|
124
|
+
nominal.type_args.any? { |arg| gradual_parameter?(arg) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def gradual_parameter?(type)
|
|
128
|
+
ContentJoin.union_members(type).any?(Type::Dynamic)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The value-rewriting counterpart of {MutationWidening.widen_tuple}'s `values: :widen`. A mutator that lands a
|
|
132
|
+
# new value in an EXISTING slot falsifies that slot's pinning on a re-opened carrier exactly as
|
|
133
|
+
# it does on a literal one, so `d = [:multi]; d << x; d[0] = "s"` must not keep reading `:multi`
|
|
134
|
+
# back out of slot 0.
|
|
135
|
+
def value_pin_widened_args(nominal)
|
|
136
|
+
Type::Combinator.nominal_of(nominal.class_name,
|
|
137
|
+
type_args: nominal.type_args.map { |a| Type::Combinator.widen_value_pinned(a) })
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Rebinds `name` to the widened carrier, carrying across what a re-join must not lose.
|
|
141
|
+
def rebind(scope, builder, name, widened, pre_state:, kind:)
|
|
142
|
+
rebound = scope.public_send(builder, name, widened)
|
|
143
|
+
pre_state.is_a?(Type::Nominal) ? carry_slot_narrowings(rebound, scope, kind, name) : rebound
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Re-installs the per-slot `receiver[key] ||= default` narrowings that rebinding the receiver
|
|
147
|
+
# drops.
|
|
148
|
+
#
|
|
149
|
+
# `Scope#with_local` drops them because a rebind normally means a DIFFERENT object, and the
|
|
150
|
+
# slot's non-nil guarantee was about the old one. A re-join (issue #580) is the opposite: the
|
|
151
|
+
# binding still names the same object, and only the summary of what it holds grew. Before the
|
|
152
|
+
# `Nominal` arm existed the second store into an already-widened carrier rebound nothing at
|
|
153
|
+
# all, so the narrowing survived by accident — Redmine's `params[:f] ||= []` then
|
|
154
|
+
# `params[:op] ||= {}` idiom lost `:f`'s narrowing the moment `:op` re-joined, and the next
|
|
155
|
+
# `params[:f] << :status` dispatched on nil again. Carrying them keeps the re-join's scope
|
|
156
|
+
# effect to the one thing it actually learned.
|
|
157
|
+
#
|
|
158
|
+
# The call recording its OWN key runs after the widening (`eval_index_or_write` orders it that
|
|
159
|
+
# way deliberately), so a carried entry never shadows a fresher one for the same slot.
|
|
160
|
+
# Method-chain narrowings are deliberately NOT carried: `x.last` is a claim about a position a
|
|
161
|
+
# content mutation really can move.
|
|
162
|
+
def carry_slot_narrowings(rebound, pre_scope, kind, name)
|
|
163
|
+
pre_scope.indexed_narrowings.reduce(rebound) do |acc, (key, type)|
|
|
164
|
+
next acc unless key.receiver_kind == kind && key.receiver_name == name.to_sym
|
|
165
|
+
|
|
166
|
+
acc.with_indexed_narrowing(key.receiver_kind, key.receiver_name, key.key, type)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|