rigortype 0.4.6 → 0.4.7
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/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +77 -71
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +24 -31
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +31 -12
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +108 -102
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +12 -8
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +84 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +480 -67
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +41 -10
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +9 -13
- data/lib/rigor/plugin/registry.rb +3 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +15 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +365 -31
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +371 -58
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +3 -5
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +36 -3
|
@@ -12,6 +12,7 @@ require_relative "match_rebinding/frame"
|
|
|
12
12
|
require_relative "match_rebinding/operands"
|
|
13
13
|
require_relative "match_rebinding/calls"
|
|
14
14
|
require_relative "match_rebinding/self_calls"
|
|
15
|
+
require_relative "match_rebinding/yielding_match"
|
|
15
16
|
require_relative "last_line"
|
|
16
17
|
|
|
17
18
|
module Rigor
|
|
@@ -367,12 +368,21 @@ module Rigor
|
|
|
367
368
|
#
|
|
368
369
|
# Issue #1429 — the entry also restores a guard's narrowing of a global or constant where the body may run after
|
|
369
370
|
# code that rebinds it ({GuardRebinding.block_entry}).
|
|
371
|
+
#
|
|
372
|
+
# Issue #2443 — the block of a call that matches before every yield (`scan`, `gsub`, `sub`, …) on a String or
|
|
373
|
+
# untyped receiver enters with that call's own match edge, laid over the entry above ({YieldingMatch}); a match
|
|
374
|
+
# its body runs still rebinds them through the statement-level rules.
|
|
370
375
|
def block_entry(scope, block_node, call_node = nil)
|
|
376
|
+
operand_scope = scope
|
|
371
377
|
scope = GuardRebinding.block_entry(scope, block_node, call_node)
|
|
372
378
|
return FreshFrameBlocks.entry(scope, call_node) if FreshFrameBlocks.fresh_entry?(call_node, scope)
|
|
373
379
|
|
|
374
380
|
scope = FreshFrameBlocks.closure_entry(scope, block_node, call_node)
|
|
375
381
|
scope = LastLine.block_entry(scope, block_node, call_node)
|
|
382
|
+
YieldingMatch.entry(frame_entry(scope, block_node, call_node), block_node, call_node, operand_scope)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def frame_entry(scope, block_node, call_node)
|
|
376
386
|
return scope unless scope.match_globals_bound?
|
|
377
387
|
return scope unless scope.match_rebinding_closure? || entry_may_match?(block_node.body, scope, call_node) ||
|
|
378
388
|
(call_node.is_a?(Prism::CallNode) && operands_may_rebind?(call_node, scope))
|
|
@@ -386,7 +396,7 @@ module Rigor
|
|
|
386
396
|
|
|
387
397
|
body.is_a?(Prism::Node) && scan(body, scope, base: true)
|
|
388
398
|
end
|
|
389
|
-
private_class_method :entry_may_match?
|
|
399
|
+
private_class_method :frame_entry, :entry_may_match?
|
|
390
400
|
end
|
|
391
401
|
end
|
|
392
402
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
# ADR-127 WD3 — the callee floor's memo audit. A memo hit is correct only when the key holds every input the value
|
|
6
|
+
# was computed from, and a key that misses one answers a caller with another caller's value (#2516, #2524). With the
|
|
7
|
+
# audit on, every hit in the callee floor's memos (`resolved_census`, the unrecorded hand-off memo, the successor
|
|
8
|
+
# store and `def_census`) recomputes the value and raises {Mismatch} when it differs from the stored one, so a key
|
|
9
|
+
# defect fails on the first hit instead of waiting for an order that exposes it.
|
|
10
|
+
#
|
|
11
|
+
# Like {BudgetTrace}, a switch for specs and CI (`RIGOR_MEMO_AUDIT=1` in the environment, read once at load, or
|
|
12
|
+
# {enable!}): off by default, a single boolean check when off. A recomputation
|
|
13
|
+
# runs with the audit suspended on its thread and the memos bypassed ({suspended?}), so it reads nothing stored.
|
|
14
|
+
module MemoAudit
|
|
15
|
+
# A memo hit whose stored value differs from a fresh computation.
|
|
16
|
+
class Mismatch < StandardError; end
|
|
17
|
+
|
|
18
|
+
SUSPENDED_KEY = :rigor_memo_audit_suspended
|
|
19
|
+
|
|
20
|
+
@enabled = %w[1 true].include?(ENV["RIGOR_MEMO_AUDIT"].to_s.downcase)
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def enabled? = @enabled
|
|
25
|
+
|
|
26
|
+
# True while an audit recomputation runs on this thread. The callee floor's memos are then bypassed (read and
|
|
27
|
+
# write), so the recomputation reads no stored entry of another memo: a defect shared across a table (a stale
|
|
28
|
+
# `resolved_census(:edges)` entry) would otherwise make the recomputed value match the stored one.
|
|
29
|
+
def suspended? = Thread.current[SUSPENDED_KEY]
|
|
30
|
+
|
|
31
|
+
def enable! = (@enabled = true)
|
|
32
|
+
|
|
33
|
+
def disable! = (@enabled = false)
|
|
34
|
+
|
|
35
|
+
# `stored`, after checking it against `yield` when the audit is on. `label` and the key parts (`key`, `key2`) name
|
|
36
|
+
# the memo entry in the failure; they are passed apart so an audit that is off allocates nothing.
|
|
37
|
+
def check(label, stored, key, key2 = nil, &)
|
|
38
|
+
return stored unless @enabled && !Thread.current[SUSPENDED_KEY]
|
|
39
|
+
|
|
40
|
+
fresh = suspended(&)
|
|
41
|
+
return stored if fresh == stored
|
|
42
|
+
|
|
43
|
+
raise Mismatch, "#{label} memo hit differs from a fresh computation for #{[key, key2].inspect[0, 120]}: " \
|
|
44
|
+
"stored #{stored.inspect[0, 200]}, fresh #{fresh.inspect[0, 200]}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def suspended
|
|
48
|
+
Thread.current[SUSPENDED_KEY] = true
|
|
49
|
+
yield
|
|
50
|
+
ensure
|
|
51
|
+
Thread.current[SUSPENDED_KEY] = nil
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -7,7 +7,7 @@ module Rigor
|
|
|
7
7
|
# Canonical RBS-core aliases shipped by `core/builtin.rbs` whose body is `<Nominal> | _DuckType`.
|
|
8
8
|
# Matching an overload against an Integer literal should pick the `(int) -> Array[Elem]` body over
|
|
9
9
|
# the `(string) -> String` body because Integer satisfies `int`'s strict arm and not `string`'s.
|
|
10
|
-
# The translator
|
|
10
|
+
# The translator reads each alias's interface arm as `Dynamic[Top]` (interfaces are not structurally matched
|
|
11
11
|
# yet), so a dedicated pass 1.5 between strict and gradual consults this map to pick the alias
|
|
12
12
|
# whose strict arm matches.
|
|
13
13
|
#
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# The positional argument lists a call with a precise union argument stands for (#2379): one per combination
|
|
9
|
+
# of the union arguments' members, since the runtime selects an overload per value. `RbsDispatch` selects an
|
|
10
|
+
# overload for each list and joins their returns.
|
|
11
|
+
module ArgumentDistribution
|
|
12
|
+
# The most lists {.lists} spells out when more than one argument is a union. One union argument is linear in
|
|
13
|
+
# its members and is not capped.
|
|
14
|
+
LIMIT = 8
|
|
15
|
+
private_constant :LIMIT
|
|
16
|
+
|
|
17
|
+
# {.lists}' answer past {LIMIT}: the call's overloads cannot be read per value, and it answers `Dynamic[top]`.
|
|
18
|
+
OVER_LIMIT = :over_limit
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# The lists, nil when no argument is a union, or {OVER_LIMIT}.
|
|
23
|
+
def lists(arg_types)
|
|
24
|
+
unions = arg_types.count { |arg| arg.is_a?(Type::Union) }
|
|
25
|
+
return nil if unions.zero?
|
|
26
|
+
|
|
27
|
+
if unions > 1
|
|
28
|
+
count = arg_types.reduce(1) { |product, arg| arg.is_a?(Type::Union) ? product * arg.members.size : product }
|
|
29
|
+
return OVER_LIMIT if count > LIMIT
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
arg_types.reduce([[]]) do |partials, arg|
|
|
33
|
+
members = arg.is_a?(Type::Union) ? arg.members : [arg]
|
|
34
|
+
partials.flat_map { |partial| members.map { |member| partial + [member] } }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -317,7 +317,7 @@ module Rigor
|
|
|
317
317
|
end
|
|
318
318
|
|
|
319
319
|
# Returns the receiver's known finite element count, or nil when the carrier does not pin a size.
|
|
320
|
-
# Tuple and HashShape are pinned by construction
|
|
320
|
+
# Tuple and HashShape are pinned by construction, and a `Constant` Range by its bounds. Other
|
|
321
321
|
# shapes (Array[T], Range[T], Nominal) decline so the RBS tier widens.
|
|
322
322
|
def finite_size(receiver)
|
|
323
323
|
case receiver
|
|
@@ -192,8 +192,8 @@ module Rigor
|
|
|
192
192
|
|
|
193
193
|
# #2115 — the numeric twin of `STRING_FOLD_BYTE_LIMIT`. A folded Integer (and each Integer part of a
|
|
194
194
|
# folded Rational / Complex) is at most this many bits, the largest bit length whose decimal
|
|
195
|
-
# rendering still fits in `STRING_FOLD_BYTE_LIMIT` digits: `2 ** 13_605` folds,
|
|
196
|
-
# (bit length 13_607) does not. Computing `2 ** 100_000_000` takes Ruby milliseconds, but
|
|
195
|
+
# rendering still fits in `STRING_FOLD_BYTE_LIMIT` digits: `2 ** 13_605` folds,
|
|
196
|
+
# `2 ** 13_606` (bit length 13_607) does not. Computing `2 ** 100_000_000` takes Ruby milliseconds, but
|
|
197
197
|
# rendering it in decimal — which describing, sorting and printing the `Constant` does — takes
|
|
198
198
|
# minutes. `**` / `pow` / `<<` / `>>` also check their operands before invoking, so the big value
|
|
199
199
|
# is never computed.
|
|
@@ -380,7 +380,7 @@ module Rigor
|
|
|
380
380
|
# Normalises an input type into one of:
|
|
381
381
|
# - `Array<Object>` for a `Constant` (1-element) or `Union[Constant…]` (n-element) — concrete values
|
|
382
382
|
# to enumerate.
|
|
383
|
-
# - `Type::IntegerRange` — bounded interval.
|
|
383
|
+
# - `Type::IntegerRange` / `Type::FloatRange` — bounded interval.
|
|
384
384
|
# - `nil` — the input shape is not foldable.
|
|
385
385
|
def numeric_set_of(type)
|
|
386
386
|
case type
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
require_relative "../rbs_type_translator"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module MethodDispatcher
|
|
9
|
+
module OverloadSelector
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Issue #2379 — every overload a supertype argument may select, when a nominal argument is wider than the
|
|
13
|
+
# parameter of the overload the call selected, `chosen`: `Integer#-` with a `Numeric` argument matched no
|
|
14
|
+
# overload and took the first, `(Integer) -> Integer`, although the runtime takes `(Float)`, `(Rational)` or
|
|
15
|
+
# `(Complex)` for those subclasses. Answers, in affinity order, every strictly typed overload whose every
|
|
16
|
+
# parameter takes its argument or is taken by a nominal one; nil when no argument is wider than `chosen`'s
|
|
17
|
+
# parameter or `chosen` is the only such overload.
|
|
18
|
+
# rubocop:disable-next Metrics/ParameterLists -- the selection inputs.
|
|
19
|
+
def contested_matches(method_definition, chosen, arg_types:, self_type:, instance_type:, type_vars:,
|
|
20
|
+
block_required:, environment:)
|
|
21
|
+
return nil unless arg_types.any?(Type::Nominal)
|
|
22
|
+
|
|
23
|
+
shared = { arg_types: arg_types, self_type: self_type, instance_type: instance_type,
|
|
24
|
+
type_vars: type_vars, block_required: block_required,
|
|
25
|
+
param_overrides: RbsExtended.param_type_override_map(method_definition, environment: environment),
|
|
26
|
+
environment: environment, keywords_last: false }
|
|
27
|
+
return nil unless wider_argument?(chosen, shared)
|
|
28
|
+
|
|
29
|
+
overloads = ReceiverAffinity.reorder(method_definition.method_types, self_type: self_type,
|
|
30
|
+
environment: environment)
|
|
31
|
+
matches = overloads.select do |method_type|
|
|
32
|
+
engages_block_shape?(method_type, block_required) &&
|
|
33
|
+
strictly_typed_params?(method_type, shared) && overlaps?(method_type, shared)
|
|
34
|
+
end
|
|
35
|
+
matches.empty? || (matches.size == 1 && matches.first.equal?(chosen)) ? nil : matches
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Whether a nominal argument is strictly wider than the parameter `method_type` binds it to: the parameter
|
|
42
|
+
# does not take the whole argument, and the argument takes the parameter (`Numeric` against `(Integer)`).
|
|
43
|
+
def wider_argument?(method_type, shared)
|
|
44
|
+
fun = method_type.type
|
|
45
|
+
arg_types = shared[:arg_types]
|
|
46
|
+
return false unless fun.respond_to?(:required_positionals) && arity_compatible?(fun, arg_types.size)
|
|
47
|
+
|
|
48
|
+
params = positional_params_for(fun, arg_types.size)
|
|
49
|
+
index = -1
|
|
50
|
+
params.any? do |param|
|
|
51
|
+
arg = arg_types[index += 1]
|
|
52
|
+
next false unless arg.is_a?(Type::Nominal)
|
|
53
|
+
|
|
54
|
+
param_type = translate_param(param, shared)
|
|
55
|
+
!param_type.accepts(arg, mode: :gradual).yes? && arg.accepts(param_type, mode: :gradual).yes?
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The strict pass's test with a nominal argument also counting where it takes the parameter: some value of
|
|
60
|
+
# a `Numeric` reaches `(Float)`.
|
|
61
|
+
def overlaps?(method_type, shared)
|
|
62
|
+
fun = method_type.type
|
|
63
|
+
ok = KeywordArguments.accepted_by?(fun, shared, true) { |*kv| accepts_param?(*kv, shared, true, true) }
|
|
64
|
+
return false unless ok
|
|
65
|
+
|
|
66
|
+
arg_types = shared[:arg_types]
|
|
67
|
+
return false unless arity_compatible?(fun, arg_types.size)
|
|
68
|
+
|
|
69
|
+
each_param_accepts?(positional_params_for(fun, arg_types.size), arg_types) do |param, arg|
|
|
70
|
+
accepts_param?(param, arg, shared, true) ||
|
|
71
|
+
(arg.is_a?(Type::Nominal) && arg.accepts(translate_param(param, shared), mode: :gradual).yes?)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A parameter's type as the selector reads it: the `rigor:v1:param:` override, else the translated RBS type.
|
|
76
|
+
def translate_param(param, shared)
|
|
77
|
+
shared[:param_overrides][param.name] || RbsTypeTranslator.translate(
|
|
78
|
+
param.type,
|
|
79
|
+
self_type: shared[:self_type],
|
|
80
|
+
instance_type: shared[:instance_type],
|
|
81
|
+
type_vars: shared[:type_vars],
|
|
82
|
+
alias_expander: shared[:environment]&.rbs_loader
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -105,7 +105,7 @@ module Rigor
|
|
|
105
105
|
# throws)
|
|
106
106
|
# - `Hash[K, V]` / `HashShape{...}` → Tuple[K, V]
|
|
107
107
|
# (Ruby yields `[key, value]` pairs as the element)
|
|
108
|
-
# - `Constant<Array>` / `Constant<Range>`
|
|
108
|
+
# - `Constant<Array>` / `Constant<Range>`
|
|
109
109
|
# → corresponding Constant element
|
|
110
110
|
#
|
|
111
111
|
# Receivers we cannot project (Top, Dynamic, unknown nominals, IO, …) decline so the RBS tier still
|
|
@@ -11,7 +11,7 @@ module Rigor
|
|
|
11
11
|
# Kernel intrinsic shape-folding — precision tier for the `Kernel` module-functions whose return type
|
|
12
12
|
# is a function of the argument's *shape*, not just its class.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
14
|
+
# `Kernel#Array` is the shape-sensitive fold. Its default RBS sig is
|
|
15
15
|
# `Array(untyped) -> Array[untyped]`, which collapses to `Array[Dynamic[top]]` for every caller. This
|
|
16
16
|
# tier short-circuits with a precise answer when the argument's type lattice tells us what the result
|
|
17
17
|
# element type MUST be:
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
module MethodDispatcher
|
|
6
|
+
module OverloadSelector
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# The overload a call answers when no selection pass matched it. A call with a block, on a method that
|
|
10
|
+
# declares a block overload, reads the block overloads (`block_call_fallback`, #2429) unless `block_fallback`
|
|
11
|
+
# is false. Otherwise prefer an overload that does not REQUIRE a block over `overloads.first`: methods like
|
|
12
|
+
# `Array#filter` / `Enumerable#map` declare the block-bearing overload first (`() { ... } -> Array[Elem]`)
|
|
13
|
+
# and the bare-call overload second (`() -> Enumerator[...]`), and a no-block `[1, 2].filter` yields an
|
|
14
|
+
# `Enumerator`.
|
|
15
|
+
def no_match_fallback(overloads, shared, block_fallback)
|
|
16
|
+
if block_fallback && shared[:block_required] && overloads.any? { |mt| overload_has_block?(mt) }
|
|
17
|
+
return block_call_fallback(overloads, shared)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
[overloads.find { |mt| !overload_requires_block?(mt) } || overloads.first]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# Issue #2429 — the block-less arm's return is what the method answers without a block, so
|
|
27
|
+
# `URI.open(url, read_timeout: 3) { |io| }` read `StringIO | Tempfile` instead of the block's value. The
|
|
28
|
+
# answer is the block overload whose positional arity admits the call when only one does, or the one every
|
|
29
|
+
# such overload agrees on; otherwise none (`[]`, which the return path reads as `Dynamic[top]`).
|
|
30
|
+
# `[3, 1, 2].max_by(2.0) { }` takes `(Integer n) { } -> Array[Elem]`, not `() { } -> Elem?`, and overloads of
|
|
31
|
+
# differing returns that all admit the call are not decided by position.
|
|
32
|
+
def block_call_fallback(overloads, shared)
|
|
33
|
+
admitted = overloads.select { |mt| overload_has_block?(mt) && arity_admits?(mt, shared) }
|
|
34
|
+
return [] if admitted.empty?
|
|
35
|
+
|
|
36
|
+
admitted.map { |mt| mt.type.return_type }.uniq.size == 1 ? [admitted.first] : []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The call's positional count (less a keyword hash the overload takes as keywords) fits `method_type`'s
|
|
40
|
+
# arity; a splat fits when some count of its elements does (#1801).
|
|
41
|
+
def arity_admits?(method_type, shared)
|
|
42
|
+
fun = method_type.type
|
|
43
|
+
arg_types = KeywordArguments.selection_positional(method_type, shared)
|
|
44
|
+
splats = shared[:splats]
|
|
45
|
+
return arity_compatible?(fun, arg_types.size) unless splats
|
|
46
|
+
|
|
47
|
+
lists = SplatArity.expansions(fun, arg_types, splats)
|
|
48
|
+
lists.nil? || lists.any? { |list| arity_compatible?(fun, list.size) }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -4,9 +4,11 @@ require_relative "../../type"
|
|
|
4
4
|
require_relative "../acceptance"
|
|
5
5
|
require_relative "../rbs_type_translator"
|
|
6
6
|
require_relative "alias_strict_nominals"
|
|
7
|
+
require_relative "contested_overloads"
|
|
7
8
|
require_relative "facet_distribution"
|
|
8
9
|
require_relative "imprecise_argument"
|
|
9
10
|
require_relative "keyword_arguments"
|
|
11
|
+
require_relative "no_match_fallback"
|
|
10
12
|
require_relative "proven_overload"
|
|
11
13
|
require_relative "receiver_affinity"
|
|
12
14
|
require_relative "splat_arity"
|
|
@@ -43,8 +45,10 @@ module Rigor
|
|
|
43
45
|
# otherwise win purely by list position and inject false constants into the flow; they remain
|
|
44
46
|
# selectable when the argument PROVES the value (strict pass) or when no other overload matches
|
|
45
47
|
# (step 4's fallback picks the first overload regardless).
|
|
46
|
-
# 4. If no overload matches at all, fall back to
|
|
47
|
-
# their phase 1 / 2b behavior. This preserves the
|
|
48
|
+
# 4. If no overload matches at all, fall back to the first overload that does not require a block (else
|
|
49
|
+
# `method_types.first`), so existing call sites keep their phase 1 / 2b behavior. This preserves the
|
|
50
|
+
# fail-soft invariant of the dispatcher. A call with a block reads the block overloads by arity instead
|
|
51
|
+
# (`no_match_fallback`, #2429).
|
|
48
52
|
#
|
|
49
53
|
# The selector is intentionally agnostic about the dispatch kind (instance vs singleton). Both kinds
|
|
50
54
|
# share the same arity and acceptance shape; the difference is only in which `Definition` the caller
|
|
@@ -64,10 +68,10 @@ module Rigor
|
|
|
64
68
|
# like `::Array[Elem]` substitute Elem before the accepts check, instead of degrading the param
|
|
65
69
|
# to `Array[Dynamic[Top]]`.
|
|
66
70
|
# @param block_required — when `true`, only overloads that declare a block clause are
|
|
67
|
-
# considered (Slice 6 phase C sub-phase 1)
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
+
# considered first (Slice 6 phase C sub-phase 1), and a call none of them matches is retried with
|
|
72
|
+
# the block ignored. Where nothing matches, a block call answers by arity among the block overloads
|
|
73
|
+
# (`block_call_fallback`, #2429), and otherwise the fallback prefers an overload that does not require a
|
|
74
|
+
# block over `method_types.first`.
|
|
71
75
|
# @return the chosen overload, or nil when the definition has no method
|
|
72
76
|
# types at all.
|
|
73
77
|
def select(method_definition, **) = select_candidates(method_definition, member_wise: false, **).first
|
|
@@ -88,12 +92,14 @@ module Rigor
|
|
|
88
92
|
# rubocop:disable-next Metrics/ParameterLists -- the selection keywords plus the member-wise switch.
|
|
89
93
|
# @param keywords_last — the call's last argument is a keyword hash (`f(a, k: 1)`, not `f(a, { k: 1 })`).
|
|
90
94
|
# @param splats — the indices of `arg_types` that are splat arguments (`f(*xs)`), or nil (#1801).
|
|
95
|
+
# @param block_fallback — false keeps a block call nothing matches on the block-less fallback (#2429): the
|
|
96
|
+
# block-parameter probe binds no parameters from an overload the call does not match.
|
|
91
97
|
def select_candidates(definition, arg_types:, self_type:, instance_type:, type_vars: {},
|
|
92
98
|
block_required: false, environment: nil, member_wise: true, keywords_last: false,
|
|
93
|
-
splats: nil)
|
|
99
|
+
splats: nil, block_fallback: true)
|
|
94
100
|
unless FacetDistribution.faceted?(arg_types)
|
|
95
101
|
return select_declared(definition, arg_types, self_type, instance_type, type_vars, block_required,
|
|
96
|
-
environment, false, keywords_last, splats)
|
|
102
|
+
environment, false, keywords_last, splats, block_fallback)
|
|
97
103
|
end
|
|
98
104
|
|
|
99
105
|
FacetDistribution.select(arg_types, definition, member_wise:, environment:) do |args, member|
|
|
@@ -101,7 +107,7 @@ module Rigor
|
|
|
101
107
|
# gradually matches every arm and the strict pass's pick is the first by position, so the member-wise answer
|
|
102
108
|
# is every gradual match (`:joined`), as the block probe reads it (#1750); the caller joins their returns.
|
|
103
109
|
select_declared(definition, args, self_type, instance_type, type_vars, block_required, environment,
|
|
104
|
-
member || (member_wise && :joined), keywords_last, splats)
|
|
110
|
+
member || (member_wise && :joined), keywords_last, splats, block_fallback)
|
|
105
111
|
end
|
|
106
112
|
end
|
|
107
113
|
|
|
@@ -114,7 +120,7 @@ module Rigor
|
|
|
114
120
|
# first-overload fallback, as the return path's reading of the arguments as given always has (#1782).
|
|
115
121
|
# rubocop:disable-next Metrics/ParameterLists -- the selection inputs plus the member-wise flag.
|
|
116
122
|
def select_declared(method_definition, arg_types, self_type, instance_type, type_vars, block_required,
|
|
117
|
-
environment, member, keywords_last, splats
|
|
123
|
+
environment, member, keywords_last, splats, block_fallback)
|
|
118
124
|
declared = method_definition.method_types
|
|
119
125
|
return [] if declared.empty?
|
|
120
126
|
|
|
@@ -159,12 +165,7 @@ module Rigor
|
|
|
159
165
|
end
|
|
160
166
|
return [] if member && member != :joined
|
|
161
167
|
|
|
162
|
-
|
|
163
|
-
# `overloads.first`. Methods like `Array#filter` / `Enumerable#map` declare the block-bearing
|
|
164
|
-
# overload first (`() { ... } -> Array[Elem]`) and the bare-call overload second
|
|
165
|
-
# (`() -> Enumerator[...]`). Without this, a no-block `[1, 2].filter` would adopt the block
|
|
166
|
-
# overload's `Array[Elem]` return when the call actually yields an `Enumerator`.
|
|
167
|
-
[overloads.find { |mt| !overload_requires_block?(mt) } || overloads.first]
|
|
168
|
+
no_match_fallback(overloads, shared, block_fallback)
|
|
168
169
|
end
|
|
169
170
|
|
|
170
171
|
def overload_has_block?(method_type)
|
|
@@ -230,13 +231,25 @@ module Rigor
|
|
|
230
231
|
# and the dispatch layer unions their returns (#521).
|
|
231
232
|
matches = find_matching_overload(overloads, shared, strict: false)
|
|
232
233
|
every_match = gradual || ImpreciseArgument.any_in?(shared[:arg_types], shared[:keywords_last])
|
|
233
|
-
return matches.
|
|
234
|
+
return [first_read_match(matches, shared)].compact unless every_match
|
|
234
235
|
|
|
235
236
|
# Issue #1675 — and every overload the arguments' untyped parts reach on their own, after the
|
|
236
237
|
# whole-argument matches so the singular `select` keeps its answer.
|
|
237
238
|
ImpreciseArgument.with_stand_ins(matches, shared) { find_matching_overload(overloads, it, strict: false) }
|
|
238
239
|
end
|
|
239
240
|
|
|
241
|
+
# Issue #2428 — the single winner of pass 2 is the first match that does not rest on a tuple's `maybe` for
|
|
242
|
+
# an `Array` of unknown arity, when there is one. Pass 1 declines a strict overload against a competitor
|
|
243
|
+
# whose parameter is an alias, an interface or `untyped`, and taking the tuple overload by list position
|
|
244
|
+
# then typed redis's `hmget(key, Array[String])` as `[String?]`, where its `(arg, Array[arg])` overload
|
|
245
|
+
# says `Array[String?]`. Only the tuple's `maybe` is re-asked; a record's `maybe` for an unread hash keeps
|
|
246
|
+
# its documented gradual-pass answer.
|
|
247
|
+
def first_read_match(matches, shared)
|
|
248
|
+
return matches.first if matches.size < 2 || !Acceptance.unread_array_in?(shared[:arg_types])
|
|
249
|
+
|
|
250
|
+
matches.find { |mt| Acceptance.refusing_unread_arrays { matches?(mt, shared) } } || matches.first
|
|
251
|
+
end
|
|
252
|
+
|
|
240
253
|
# Pass 0. With every argument a plain value (a `Constant`, or a `Nominal` with no type arguments), the
|
|
241
254
|
# first overload in declared order that the arguments do not rule out is the one RBS would take, and
|
|
242
255
|
# the pass takes it only when that is certain: every parameter is a lone class naming its argument's
|
|
@@ -307,7 +320,10 @@ module Rigor
|
|
|
307
320
|
next false unless engages_block_shape?(method_type, shared[:block_required])
|
|
308
321
|
|
|
309
322
|
fun = method_type.type
|
|
310
|
-
|
|
323
|
+
# Issue #2428 — a tuple keyword's `maybe` for an unread Array is no evidence here; pass 2 decides.
|
|
324
|
+
ok = Acceptance.refusing_unread_arrays do
|
|
325
|
+
KeywordArguments.accepted_by?(fun, shared, false) { |*kv| accepts_param?(*kv, shared, false, true) }
|
|
326
|
+
end
|
|
311
327
|
next false unless ok
|
|
312
328
|
|
|
313
329
|
arg_types = KeywordArguments.selection_positional(method_type, shared)
|
|
@@ -445,13 +461,7 @@ module Rigor
|
|
|
445
461
|
|
|
446
462
|
# `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
|
|
447
463
|
def accepts_param?(param, arg, shared, strict, keyword = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
448
|
-
param_type =
|
|
449
|
-
param.type,
|
|
450
|
-
self_type: shared[:self_type],
|
|
451
|
-
instance_type: shared[:instance_type],
|
|
452
|
-
type_vars: shared[:type_vars],
|
|
453
|
-
alias_expander: shared[:environment]&.rbs_loader
|
|
454
|
-
)
|
|
464
|
+
param_type = translate_param(param, shared)
|
|
455
465
|
# An `untyped` arg gradually accepts against every param, so a value-pinning param would be
|
|
456
466
|
# "matched" with zero evidence and its value-precise return (`(nil) -> []`) would beat broader
|
|
457
467
|
# overloads purely by list position. Decline the pair; only the strict pass (where the arg
|
|
@@ -467,8 +477,10 @@ module Rigor
|
|
|
467
477
|
# A record's `maybe` for a `Hash` with a gradual arm or an open shape is no evidence for the overload:
|
|
468
478
|
# with `({ a: Integer }) -> Integer | (Hash[Symbol, untyped]) -> String`, `{ **o, b: 2 }` has a key the
|
|
469
479
|
# closed record forbids, yet the first overload won by list position. The gradual pass still takes it.
|
|
470
|
-
# A `maybe`
|
|
471
|
-
|
|
480
|
+
# A tuple's `maybe` for an `Array` of unknown arity is the same: `([Integer, Integer]) -> Symbol |
|
|
481
|
+
# (Array[Integer]) -> String` answers `String` for an `Array[Integer]`. A `maybe` that survives with
|
|
482
|
+
# those shapes answering `no` has another source, and still counts.
|
|
483
|
+
return false if strict && result.maybe? && Acceptance.maybe_rests_on_unread_shape?(param_type, arg)
|
|
472
484
|
|
|
473
485
|
result.yes? || result.maybe?
|
|
474
486
|
end
|
|
@@ -20,7 +20,7 @@ module Rigor
|
|
|
20
20
|
# (`Errno::EDOM`) and a non-literal argument decline to the RBS envelope. The other argument
|
|
21
21
|
# shapes (`rand`, `rand(n)`, `rand(1.5)`) are deliberately NOT folded: the corpus uses them as its
|
|
22
22
|
# "unknown Integer / Float" oracle, and their intervals belong to a change that replaces that
|
|
23
|
-
# oracle first. The `Kernel` spelling is folded by {KernelDispatch
|
|
23
|
+
# oracle first. The `Kernel` spelling is folded by {KernelDispatch.try_dispatch} behind ADR-91's
|
|
24
24
|
# ownership gate; this module owns `Singleton[Random]` and the shared interval reading.
|
|
25
25
|
module RandomFolding
|
|
26
26
|
module_function
|