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
|
@@ -8,8 +8,10 @@ require_relative "../rbs_type_translator"
|
|
|
8
8
|
require_relative "../external_ancestor_resolution"
|
|
9
9
|
require_relative "../void_origin"
|
|
10
10
|
require_relative "../optimistic_origin"
|
|
11
|
+
require_relative "argument_distribution"
|
|
11
12
|
require_relative "overload_selector"
|
|
12
13
|
require_relative "self_substitute"
|
|
14
|
+
require_relative "../../run_scoped_slots"
|
|
13
15
|
|
|
14
16
|
module Rigor
|
|
15
17
|
module Inference
|
|
@@ -33,8 +35,8 @@ module Rigor
|
|
|
33
35
|
# type-parameter names (`Array` -> `[:Elem]`, `Hash` -> `[:K, :V]`, ...) to build a substitution map;
|
|
34
36
|
# that map is then threaded through {RbsTypeTranslator} so a return type like `::Array[Elem]`
|
|
35
37
|
# resolves to `Nominal["Array", [Integer]]` rather than degrading the variable to `Dynamic[Top]`.
|
|
36
|
-
#
|
|
37
|
-
# degrade as before.
|
|
38
|
+
# A raw receiver, or one with more arguments than the class declares, leaves the map empty and free
|
|
39
|
+
# variables degrade as before; fewer arguments bind the declared prefix (#1121).
|
|
38
40
|
#
|
|
39
41
|
# Slice 5 phase 1 projects shape-carrying receivers onto their underlying nominal so the existing
|
|
40
42
|
# dispatch + substitution machinery works without duplication: `Tuple[Integer, String]` dispatches as
|
|
@@ -44,13 +46,14 @@ module Rigor
|
|
|
44
46
|
#
|
|
45
47
|
# Remaining limitations:
|
|
46
48
|
#
|
|
47
|
-
# * Keyword arguments reach `args`
|
|
48
|
-
# parameters
|
|
49
|
-
# * Method-level type parameters bind
|
|
50
|
-
#
|
|
49
|
+
# * Keyword arguments reach `args` as one trailing hash entry; the selector matches it against keyword
|
|
50
|
+
# parameters when the call's last argument is a keyword hash (#1727).
|
|
51
|
+
# * Method-level type parameters bind from the block return type (Slice 6 phase C) and from a
|
|
52
|
+
# positional parameter whose declared type is EXACTLY a type variable (issue #303 —
|
|
51
53
|
# `def foo[T]: (T) -> T` binds `T` from the first argument, and carries it into a generic return
|
|
52
|
-
# such as `-> Array[T]`). A variable reachable only through
|
|
53
|
-
# a rest positional (`*T`), or a keyword parameter is still
|
|
54
|
+
# such as `-> Array[T]`), plus a `Range[A]` element (#834). A variable reachable only through other
|
|
55
|
+
# container positions (`Array[T] arg`), a rest positional (`*T`), or a keyword parameter is still
|
|
56
|
+
# unbound and degrades to `Dynamic[Top]`.
|
|
54
57
|
# A block-return variable that a parameter also names takes the value class the argument and the
|
|
55
58
|
# block share once the call passes an argument, or stays unbound when they share none (see
|
|
56
59
|
# {compose_block_type_vars}).
|
|
@@ -681,7 +684,7 @@ module Rigor
|
|
|
681
684
|
|
|
682
685
|
# The same one-slot memo shape as {#core_stdlib_memo}; see there for why it is one slot keyed
|
|
683
686
|
# on the discovery index's identity, and what a recording run may store in it.
|
|
684
|
-
MIXIN_ANCESTOR_MEMO_KEY = :__rigor_mixin_ancestor_dispatch__
|
|
687
|
+
MIXIN_ANCESTOR_MEMO_KEY = RunScopedSlots.register(:__rigor_mixin_ancestor_dispatch__)
|
|
685
688
|
private_constant :MIXIN_ANCESTOR_MEMO_KEY
|
|
686
689
|
|
|
687
690
|
def mixin_ancestor_memo(environment, scope)
|
|
@@ -855,7 +858,7 @@ module Rigor
|
|
|
855
858
|
# the measurement. A `Scope` hands each analysed file its own discovery index, so an
|
|
856
859
|
# identity-keyed store would pin every file's index, and every RBS definition resolved
|
|
857
860
|
# against it, for the length of the run.
|
|
858
|
-
CORE_STDLIB_ANCESTOR_MEMO_KEY = :__rigor_core_stdlib_ancestor_dispatch__
|
|
861
|
+
CORE_STDLIB_ANCESTOR_MEMO_KEY = RunScopedSlots.register(:__rigor_core_stdlib_ancestor_dispatch__)
|
|
859
862
|
private_constant :CORE_STDLIB_ANCESTOR_MEMO_KEY
|
|
860
863
|
|
|
861
864
|
def core_stdlib_memo(environment, scope)
|
|
@@ -1086,7 +1089,42 @@ module Rigor
|
|
|
1086
1089
|
block_required: !block_type.nil?,
|
|
1087
1090
|
environment: environment
|
|
1088
1091
|
)
|
|
1089
|
-
|
|
1092
|
+
if candidates.empty?
|
|
1093
|
+
return unselected_return(method_definition, self_type, instance_type, type_vars, args, block_type,
|
|
1094
|
+
scope, call_node, call_site, environment, keywords_last)
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
selected_return(candidates, method_definition, self_type, instance_type, type_vars, args, block_type, scope,
|
|
1098
|
+
call_node, call_site, environment, keywords_last)
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
# Issue #2429 — a block call no overload matches, whose block overloads the arity fallback cannot decide
|
|
1102
|
+
# between, selects nothing. An argument union may still select per value (#2379): `S.h(flag ? "s" : 1) { }`
|
|
1103
|
+
# takes the `String` arm for `"s"` and the `Integer` arm for `1`. Otherwise the tier declines (nil), so a
|
|
1104
|
+
# later tier, such as the project's own definition of the method, answers.
|
|
1105
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1106
|
+
def unselected_return(method_definition, self_type, instance_type, type_vars, args, block_type, scope,
|
|
1107
|
+
call_node, call_site, environment, keywords_last)
|
|
1108
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1109
|
+
return nil if keywords_last
|
|
1110
|
+
|
|
1111
|
+
distributed = distributed_argument_return(method_definition, nil, self_type, instance_type, type_vars, args,
|
|
1112
|
+
block_type, scope, call_node, call_site, environment)
|
|
1113
|
+
distributed.equal?(NOT_DISTRIBUTED) ? nil : distributed
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
# The return of the overloads the call selected: per argument value where a union or supertype argument
|
|
1117
|
+
# selects more than `candidates` ({#distributed_argument_return}), else their join.
|
|
1118
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1119
|
+
def selected_return(candidates, method_definition, self_type, instance_type, type_vars, args, block_type,
|
|
1120
|
+
scope, call_node, call_site, environment, keywords_last)
|
|
1121
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1122
|
+
if candidates.size == 1 && !keywords_last
|
|
1123
|
+
distributed = distributed_argument_return(method_definition, candidates.first, self_type, instance_type,
|
|
1124
|
+
type_vars, args, block_type, scope, call_node, call_site,
|
|
1125
|
+
environment)
|
|
1126
|
+
return distributed unless distributed.equal?(NOT_DISTRIBUTED)
|
|
1127
|
+
end
|
|
1090
1128
|
|
|
1091
1129
|
record_dispatch_provenance(method_definition, candidates.first, scope, call_node, call_site)
|
|
1092
1130
|
join_candidate_returns(
|
|
@@ -1169,6 +1207,75 @@ module Rigor
|
|
|
1169
1207
|
end
|
|
1170
1208
|
end
|
|
1171
1209
|
|
|
1210
|
+
# Issue #2379 — a positional argument selects its overload per runtime value, as the receiver does
|
|
1211
|
+
# (`dispatch` distributes a union receiver). Selecting once for the whole argument took the first overload
|
|
1212
|
+
# it `maybe`-matched: `Integer#+` with an `Integer | Float` argument read `(Integer) -> Integer`, and with a
|
|
1213
|
+
# `Numeric` one as well. So a precise union argument selects per member ({ArgumentDistribution.lists}; a
|
|
1214
|
+
# member no overload takes keeps the first-overload fallback, as a non-matching argument does), and a
|
|
1215
|
+
# nominal argument wider than the selected overload's parameter takes every strictly typed overload it
|
|
1216
|
+
# overlaps ({OverloadSelector.contested_matches}). The returns join as a plain union, and a return that does
|
|
1217
|
+
# not translate declines the call (nil), as the #521 join does. Union arguments that combine past the list
|
|
1218
|
+
# limit answer `Dynamic[top]`: neither the first overload nor any narrower reading is sound there. When
|
|
1219
|
+
# every list selects `chosen` alone the call answers {NOT_DISTRIBUTED} and reads as before, so a precise
|
|
1220
|
+
# argument keeps its answer; so does a call holding an imprecise argument (the #521 join owns it) or a splat.
|
|
1221
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1222
|
+
def distributed_argument_return(method_definition, chosen, self_type, instance_type, type_vars, args,
|
|
1223
|
+
block_type, scope, call_node, call_site, environment)
|
|
1224
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1225
|
+
selector = { self_type: self_type.is_a?(Type::Dynamic) ? self_type.static_facet : self_type,
|
|
1226
|
+
instance_type: instance_type, type_vars: type_vars, block_required: !block_type.nil?,
|
|
1227
|
+
environment: environment }
|
|
1228
|
+
return NOT_DISTRIBUTED unless distributable_arguments?(method_definition, args, call_node)
|
|
1229
|
+
|
|
1230
|
+
lists = ArgumentDistribution.lists(args)
|
|
1231
|
+
return Type::Combinator.untyped if lists.equal?(ArgumentDistribution::OVER_LIMIT)
|
|
1232
|
+
|
|
1233
|
+
per_list = argument_distribution_matches(method_definition, chosen, args, lists, selector)
|
|
1234
|
+
return NOT_DISTRIBUTED if per_list.nil?
|
|
1235
|
+
|
|
1236
|
+
overloads = per_list.flat_map(&:last).uniq
|
|
1237
|
+
if overloads.size == 1
|
|
1238
|
+
record_dispatch_provenance(method_definition, overloads.first, scope, call_node, call_site)
|
|
1239
|
+
end
|
|
1240
|
+
returns = per_list.flat_map do |arg_types, matches|
|
|
1241
|
+
matches.map do |method_type|
|
|
1242
|
+
join_candidate_returns(
|
|
1243
|
+
[method_type],
|
|
1244
|
+
method_definition: method_definition,
|
|
1245
|
+
self_type: self_type, instance_type: instance_type, type_vars: type_vars,
|
|
1246
|
+
args: arg_types, block_type: block_type, scope: scope, call_node: call_node, call_site: call_site,
|
|
1247
|
+
alias_expander: environment.rbs_loader
|
|
1248
|
+
)
|
|
1249
|
+
end
|
|
1250
|
+
end
|
|
1251
|
+
returns.any?(&:nil?) ? nil : Type::Combinator.union(*returns.uniq)
|
|
1252
|
+
end
|
|
1253
|
+
|
|
1254
|
+
# Each argument list (`lists`, from {ArgumentDistribution.lists}, or the call's own) with the overloads it
|
|
1255
|
+
# selects, or nil when every list selects `chosen` alone. `selector` holds {OverloadSelector}'s keyword
|
|
1256
|
+
# inputs.
|
|
1257
|
+
def argument_distribution_matches(method_definition, chosen, args, lists, selector)
|
|
1258
|
+
return nil if lists.nil? && args.none?(Type::Nominal)
|
|
1259
|
+
|
|
1260
|
+
per_list = (lists || [args]).map do |arg_types|
|
|
1261
|
+
picked = chosen if arg_types.equal?(args)
|
|
1262
|
+
picked ||= OverloadSelector.select(method_definition, arg_types:, **selector)
|
|
1263
|
+
# #2429's block fallback may answer no overload; the call then reads as it did undistributed.
|
|
1264
|
+
return nil unless picked
|
|
1265
|
+
|
|
1266
|
+
contested = OverloadSelector.contested_matches(method_definition, picked, arg_types:, **selector)
|
|
1267
|
+
[arg_types, contested || [picked]]
|
|
1268
|
+
end
|
|
1269
|
+
per_list unless per_list.all? { |_, matches| matches.size == 1 && matches.first.equal?(chosen) }
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
# Only a call with overloads to choose between and precise arguments it spells out one by one may select
|
|
1273
|
+
# differently per argument value: an imprecise argument is the #521 join's, a splat hides the positions.
|
|
1274
|
+
def distributable_arguments?(method_definition, args, call_node)
|
|
1275
|
+
method_definition.method_types.size > 1 && !args.empty? &&
|
|
1276
|
+
!ImpreciseArgument.any_in?(args, false) && !splat_indices(call_node, args)
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1172
1279
|
def join_distributed_returns(returns, keywords)
|
|
1173
1280
|
return nil if returns.any?(&:nil?)
|
|
1174
1281
|
|
|
@@ -1412,11 +1519,24 @@ module Rigor
|
|
|
1412
1519
|
# to {#shared_value_class} where both sides have one, and to `Dynamic[top]` otherwise. The key
|
|
1413
1520
|
# stays in the map either way so {#compose_arg_type_vars} does not bind the variable from the
|
|
1414
1521
|
# argument alone.
|
|
1522
|
+
#
|
|
1523
|
+
# A block return spelled as the variable beside falsey arms (`(nil | false | U)`, `U?`) solves the
|
|
1524
|
+
# variable from the block's type less those arms ({#strip_falsey_arms}): `filter_map` keeps only
|
|
1525
|
+
# the block's truthy values, so a block answering `String | nil` binds `U` to `String`. The strip
|
|
1526
|
+
# trusts the declared method to discard those values, so a receiver class whose Ruby source
|
|
1527
|
+
# defines the method itself leaves the variable unbound: `def filter_map(&b) = map(&b).compact`
|
|
1528
|
+
# under an `include Enumerable[Integer]` declaration keeps `false`, and `Array[true]` would fold
|
|
1529
|
+
# `b.last == false` always-falsey on correct code.
|
|
1415
1530
|
def compose_block_type_vars(method_type, type_vars, block_type, args, scope:, call_node:, call_site:)
|
|
1416
1531
|
return type_vars if block_type.nil?
|
|
1417
1532
|
|
|
1418
1533
|
block_var_name = method_type_block_return_variable(method_type)
|
|
1419
1534
|
return type_vars if block_var_name.nil?
|
|
1535
|
+
|
|
1536
|
+
arms = block_return_falsey_arms(method_type)
|
|
1537
|
+
return type_vars if arms.any? && project_redefined?(scope, call_site)
|
|
1538
|
+
|
|
1539
|
+
block_type = strip_falsey_arms(block_type, arms)
|
|
1420
1540
|
return type_vars.merge(block_var_name => block_type) unless
|
|
1421
1541
|
argument_reaches_variable?(method_type, block_var_name, args)
|
|
1422
1542
|
|
|
@@ -1751,14 +1871,78 @@ module Rigor
|
|
|
1751
1871
|
end
|
|
1752
1872
|
|
|
1753
1873
|
def block_return_variable(method_type)
|
|
1874
|
+
return_type = block_return_type(method_type)
|
|
1875
|
+
return return_type if return_type.is_a?(RBS::Types::Variable)
|
|
1876
|
+
|
|
1877
|
+
falsey_armed_variable(return_type)&.first
|
|
1878
|
+
end
|
|
1879
|
+
|
|
1880
|
+
def block_return_type(method_type)
|
|
1754
1881
|
block = method_type.respond_to?(:block) ? method_type.block : nil
|
|
1755
1882
|
return nil if block.nil?
|
|
1756
1883
|
|
|
1757
1884
|
fun = block.type
|
|
1758
|
-
|
|
1885
|
+
fun.respond_to?(:return_type) ? fun.return_type : nil
|
|
1886
|
+
end
|
|
1887
|
+
|
|
1888
|
+
# The falsey arms ({#falsey_armed_variable}) the block's declared return spells beside its variable;
|
|
1889
|
+
# empty for a bare variable.
|
|
1890
|
+
def block_return_falsey_arms(method_type)
|
|
1891
|
+
falsey_armed_variable(block_return_type(method_type))&.last || EMPTY_FALSEY_ARMS
|
|
1892
|
+
end
|
|
1893
|
+
|
|
1894
|
+
EMPTY_FALSEY_ARMS = [].freeze
|
|
1895
|
+
private_constant :EMPTY_FALSEY_ARMS
|
|
1896
|
+
|
|
1897
|
+
# A block return that is one type variable beside `nil` and/or `false` and nothing else —
|
|
1898
|
+
# `Enumerable#filter_map`'s `(nil | false | U)`, or `U?` — as `[variable, arms]`, the arms a subset
|
|
1899
|
+
# of `[:nil, :false]`. Anything else, `bool` among them, is nil: a block return must only lose
|
|
1900
|
+
# values the method itself discards, and a `false`-only arm has no carrier that removes `false`
|
|
1901
|
+
# alone from a `Dynamic` block type, so it declines too.
|
|
1902
|
+
def falsey_armed_variable(return_type)
|
|
1903
|
+
members =
|
|
1904
|
+
case return_type
|
|
1905
|
+
when RBS::Types::Optional then [return_type.type, RBS::Types::Bases::Nil.new(location: nil)]
|
|
1906
|
+
when RBS::Types::Union then return_type.types
|
|
1907
|
+
else return nil
|
|
1908
|
+
end
|
|
1909
|
+
variables, arms = members.partition { |member| member.is_a?(RBS::Types::Variable) }
|
|
1910
|
+
return nil unless variables.size == 1
|
|
1759
1911
|
|
|
1760
|
-
|
|
1761
|
-
|
|
1912
|
+
arm_names = arms.map { |arm| falsey_arm_name(arm) }
|
|
1913
|
+
return nil if arm_names.include?(nil) || !arm_names.include?(:nil)
|
|
1914
|
+
|
|
1915
|
+
[variables.first, arm_names.uniq]
|
|
1916
|
+
end
|
|
1917
|
+
|
|
1918
|
+
def falsey_arm_name(arm)
|
|
1919
|
+
case arm
|
|
1920
|
+
when RBS::Types::Bases::Nil then :nil
|
|
1921
|
+
when RBS::Types::Literal then arm.literal == false ? :false : nil # rubocop:disable Lint/BooleanSymbol
|
|
1922
|
+
end
|
|
1923
|
+
end
|
|
1924
|
+
|
|
1925
|
+
# The block's type less the falsey values the declared return spells beside the variable: its
|
|
1926
|
+
# truthy fragment for `nil | false`, its non-nil fragment for `nil` alone. A `Dynamic` block type
|
|
1927
|
+
# passes through unchanged, so an untyped block still binds `Dynamic[top]`. A block that answers
|
|
1928
|
+
# only the stripped values binds `bot`, so `filter_map { nil }` is `Array[bot]`: an Array that
|
|
1929
|
+
# holds no element.
|
|
1930
|
+
# Whether the receiver class's Ruby source defines the dispatched method, the probe
|
|
1931
|
+
# {#arg_binding_permitted?} also reads. Without a scope there is no discovery to consult.
|
|
1932
|
+
def project_redefined?(scope, call_site)
|
|
1933
|
+
return false if scope.nil?
|
|
1934
|
+
|
|
1935
|
+
class_name, method_name, kind = call_site
|
|
1936
|
+
return false if method_name.nil?
|
|
1937
|
+
|
|
1938
|
+
scope.discovered_method?(class_name, method_name, kind)
|
|
1939
|
+
end
|
|
1940
|
+
|
|
1941
|
+
def strip_falsey_arms(block_type, arms)
|
|
1942
|
+
return block_type if arms.empty?
|
|
1943
|
+
return Narrowing.narrow_truthy(block_type) if arms.include?(:false) # rubocop:disable Lint/BooleanSymbol
|
|
1944
|
+
|
|
1945
|
+
Narrowing.narrow_non_nil(block_type)
|
|
1762
1946
|
end
|
|
1763
1947
|
|
|
1764
1948
|
# ----- block parameter probe (Phase C sub-phase 1) -----
|
|
@@ -1862,7 +2046,9 @@ module Rigor
|
|
|
1862
2046
|
instance_type: instance_type,
|
|
1863
2047
|
type_vars: type_vars,
|
|
1864
2048
|
block_required: true,
|
|
1865
|
-
environment: environment
|
|
2049
|
+
environment: environment,
|
|
2050
|
+
# A call no overload matches binds no block parameter from the return path's block fallback (#2429).
|
|
2051
|
+
block_fallback: false
|
|
1866
2052
|
)
|
|
1867
2053
|
return [] unless method_type
|
|
1868
2054
|
|
|
@@ -1940,7 +2126,7 @@ module Rigor
|
|
|
1940
2126
|
# rubocop:enable Metrics/ParameterLists
|
|
1941
2127
|
unless FacetDistribution.faceted?(arg_types)
|
|
1942
2128
|
return OverloadSelector.select_declared(method_definition, arg_types, selector_self, instance_type,
|
|
1943
|
-
type_vars, block_required, environment, true, true, splats)
|
|
2129
|
+
type_vars, block_required, environment, true, true, splats, false)
|
|
1944
2130
|
end
|
|
1945
2131
|
|
|
1946
2132
|
facet_matches(method_definition, arg_types, selector_self, instance_type, type_vars, block_required,
|
|
@@ -1960,7 +2146,7 @@ module Rigor
|
|
|
1960
2146
|
# The fallback list (`member` false) answers every gradual match rather than the strict pass's pick.
|
|
1961
2147
|
OverloadSelector.select_declared(method_definition, list, selector_self, instance_type, type_vars,
|
|
1962
2148
|
block_required, environment, member || :gradual, keywords_last,
|
|
1963
|
-
splats)
|
|
2149
|
+
splats, false)
|
|
1964
2150
|
end
|
|
1965
2151
|
end
|
|
1966
2152
|
|
|
@@ -148,7 +148,7 @@ module Rigor
|
|
|
148
148
|
|
|
149
149
|
# Runs the actual reduction, guarded by the rescue harness and the magnitude cap. Empty-collection
|
|
150
150
|
# semantics match Ruby: `[].reduce(s, :op) == s` (seed form) and `[].reduce(:op) == nil` (no-seed
|
|
151
|
-
# form → declines so the nominal fold's no-nil contract stands; see
|
|
151
|
+
# form → declines so the nominal fold's no-nil contract stands; see the class comment).
|
|
152
152
|
def execute_constant_reduce(members, operator, seed_value, seed_present)
|
|
153
153
|
result =
|
|
154
154
|
if seed_present
|
|
@@ -14,8 +14,8 @@ module Rigor
|
|
|
14
14
|
# * `new(str)` / `new(str, opts)` — constructs a Regexp at fold time when the pattern argument is a
|
|
15
15
|
# `Constant[String]`. The optional second argument may be a `Constant[Integer]` (flag bits), a
|
|
16
16
|
# `Constant[true/false]` (IGNORECASE shorthand), or absent. Returns `Constant[Regexp]`.
|
|
17
|
-
# * `compile(str)` / `compile(str, opts)` — `Regexp.compile` is a documented alias of `Regexp.new
|
|
18
|
-
#
|
|
17
|
+
# * `compile(str)` / `compile(str, opts)` — `Regexp.compile` is a documented alias of `Regexp.new`;
|
|
18
|
+
# shares `fold_new` verbatim.
|
|
19
19
|
# * `union(*patterns)` / `union(array)` — builds the combined pattern at inference time when every
|
|
20
20
|
# element is a `Constant[String]` or `Constant[Regexp]`. Delegates to the real `Regexp.union` so the
|
|
21
21
|
# zero-arg (`/(?!)/`), single-array-argument, and pre-built-Regexp-argument forms all match Ruby's
|
|
@@ -67,8 +67,8 @@ module Rigor
|
|
|
67
67
|
projected_self(class_name, receiver_args)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
# Nil when every argument widens to
|
|
71
|
-
#
|
|
70
|
+
# Nil when every argument widens to untyped: a `Hash` of untyped keys and values says nothing the
|
|
71
|
+
# raw `Hash` does not.
|
|
72
72
|
def projected_self(class_name, receiver_args)
|
|
73
73
|
type_args = receiver_args.map { |arg| deep_widen(arg) }
|
|
74
74
|
return nil if type_args.all? { |arg| untyped?(arg) }
|
|
@@ -13,17 +13,19 @@ module Rigor
|
|
|
13
13
|
#
|
|
14
14
|
# === Supported methods
|
|
15
15
|
#
|
|
16
|
-
# * `Set[]` — variadic constructor
|
|
16
|
+
# * `Set[]` — variadic constructor. Returns `Constant[Set]` when every argument is a `Constant[T]`, and a
|
|
17
|
+
# typed `Set` otherwise.
|
|
17
18
|
# * `Set.new` — zero-argument form; returns `Constant[Set.new]`.
|
|
18
|
-
# * `Set.new(tuple)` —
|
|
19
|
-
# `
|
|
19
|
+
# * `Set.new(tuple)` — a `Tuple` argument. Returns `Constant[Set]` when every element is a `Constant[T]`,
|
|
20
|
+
# and a typed `Set` otherwise.
|
|
20
21
|
#
|
|
21
22
|
# === Non-constant / unsupported cases
|
|
22
23
|
#
|
|
23
24
|
# Returns `nil` (deferring to the next dispatcher tier) when:
|
|
24
25
|
# - the receiver is not `Singleton[Set]`,
|
|
25
26
|
# - the method is not `[]` or `new`,
|
|
26
|
-
# -
|
|
27
|
+
# - the call is splatted or carries a block, or its `new` argument is not a `Tuple` or an `Array` / `Set`
|
|
28
|
+
# carrier.
|
|
27
29
|
module SetFolding
|
|
28
30
|
module_function
|
|
29
31
|
|
|
@@ -86,9 +88,9 @@ module Rigor
|
|
|
86
88
|
|
|
87
89
|
# `Nominal[Set, [E]]`, `E` the members widened as an `Array.new` fill is (value pins dropped, a literal
|
|
88
90
|
# container widened to its nominal) joined with a `Dynamic[top]` floor. The floor is the conservative half:
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
91
|
+
# the mutation seams ({SetMutation}, #2294) see one store at a time, so the classes the set was built with
|
|
92
|
+
# are not a claim about what it holds later, and a read of the element must not report on them (#2278). The
|
|
93
|
+
# floor is also what lets those seams re-grow the set.
|
|
92
94
|
def typed_set(types)
|
|
93
95
|
return nil if types.empty?
|
|
94
96
|
|
|
@@ -194,6 +194,7 @@ module Rigor
|
|
|
194
194
|
Type::Tuple => :dispatch_tuple,
|
|
195
195
|
Type::HashShape => :dispatch_hash_shape,
|
|
196
196
|
Type::Nominal => :dispatch_nominal_size,
|
|
197
|
+
Type::Constant => :dispatch_constant_set,
|
|
197
198
|
Type::Difference => :dispatch_difference,
|
|
198
199
|
Type::Refined => :dispatch_refined,
|
|
199
200
|
Type::Intersection => :dispatch_intersection,
|
|
@@ -302,9 +303,38 @@ module Rigor
|
|
|
302
303
|
when :flatten then array_nominal_flatten(nominal, args)
|
|
303
304
|
when :compact then array_nominal_compact(nominal, args)
|
|
304
305
|
end
|
|
306
|
+
when "Set"
|
|
307
|
+
set_nominal_algebra(nominal, method_name, args)
|
|
305
308
|
end
|
|
306
309
|
end
|
|
307
310
|
|
|
311
|
+
# The Set algebra that returns a new Set holding both operands' elements (issue #2294). RBS declares each
|
|
312
|
+
# as `(_Each[A]) -> Set[A]`, the receiver's element alone, so `ARGV.to_set | [1]` read `Set[String]` and a
|
|
313
|
+
# read of its `1` reported on correct code. The result is `Set[E | F]`, `F` the argument's elements with
|
|
314
|
+
# their value pins dropped ({SetMutation.element_types}).
|
|
315
|
+
SET_UNION_ALGEBRA = %i[| union + ^].to_set.freeze
|
|
316
|
+
private_constant :SET_UNION_ALGEBRA
|
|
317
|
+
|
|
318
|
+
def set_nominal_algebra(nominal, method_name, args)
|
|
319
|
+
return nil unless SET_UNION_ALGEBRA.include?(method_name) && args.size == 1
|
|
320
|
+
|
|
321
|
+
receiver = nominal.type_args.size == 1 ? nominal.type_args.first : Type::Combinator.untyped
|
|
322
|
+
added = Inference::SetMutation.element_types(args.first).map { |t| MethodDispatcher.array_new_element(t) }
|
|
323
|
+
Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.union(receiver, *added)])
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# The same algebra on a constant-folded Set whose argument is not constant (two constants fold upstream in
|
|
327
|
+
# {ConstantFolding}): the receiver's members and the argument's elements, typed as #2290 types a
|
|
328
|
+
# `Set[...]` literal over non-constant members ({SetFolding.typed_set}).
|
|
329
|
+
def dispatch_constant_set(constant, method_name, args)
|
|
330
|
+
return nil unless SET_UNION_ALGEBRA.include?(method_name) && args.size == 1
|
|
331
|
+
return nil unless Inference::SetMutation.constant?(constant)
|
|
332
|
+
|
|
333
|
+
members = constant.value.map { |v| Type::Combinator.constant_of(v) }
|
|
334
|
+
SetFolding.typed_set(members + Inference::SetMutation.element_types(args.first)) ||
|
|
335
|
+
Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.untyped])
|
|
336
|
+
end
|
|
337
|
+
|
|
308
338
|
# `Integer#to_s` / `Integer#inspect` (#993) on a bare `Nominal[Integer]` receiver, ahead of the
|
|
309
339
|
# existing `:*` binary fold. `#inspect` takes no argument and matches `#to_s()` byte for byte, so
|
|
310
340
|
# it routes through the same no-arg branch.
|
|
@@ -317,7 +347,7 @@ module Rigor
|
|
|
317
347
|
end
|
|
318
348
|
|
|
319
349
|
# `decimal-int-string`'s predicate (`/\A-?\d+\z/`) admits the optional sign, so — unlike the
|
|
320
|
-
# `IntegerRange#to_s` tier
|
|
350
|
+
# `IntegerRange#to_s` tier below, which declines a signed range because it cannot yet prove the
|
|
321
351
|
# sign is absent — a bare `Integer` (sign completely unknown) still reaches it for the base-10
|
|
322
352
|
# case: base 10 is Ruby's default sign-and-digits grammar for every Integer. Any other base,
|
|
323
353
|
# known or not statically known, drops to `non-empty-string`: `to_s(base)` never returns `""`,
|
|
@@ -422,14 +452,29 @@ module Rigor
|
|
|
422
452
|
# Resolves the element type of a flattened `Array[element]`. Each `Array[U]` nesting level
|
|
423
453
|
# contributes `U`; the per-level element types are unioned. `depth < 0` recurses without bound;
|
|
424
454
|
# `depth == 0` stops (Ruby's `flatten(0)` is a no-op copy and returns the element unchanged).
|
|
455
|
+
#
|
|
456
|
+
# Issue #2427 — a `Tuple` element is an Array too and contributes the union of its own elements, each
|
|
457
|
+
# flattened one level deeper, and so does a `Tuple` member of a `Union` element. `ARGV.map { |x| [x] }`
|
|
458
|
+
# is `Array[[String]]`, and returning its `[String]` element unchanged made `.flatten.first.upcase`
|
|
459
|
+
# report `undefined method 'upcase' for [String]` on code Ruby runs.
|
|
460
|
+
#
|
|
461
|
+
# A nominal `Array[U]` member of a `Union` element is kept. RBS's `String#scan(Regexp)` answers
|
|
462
|
+
# `Array[String | Array[String?]]`, the `?` standing for an optional group, and flattening that member
|
|
463
|
+
# turned the idiomatic `str.scan(/k = (.+)/).flatten.map(&:strip)` into a `possible-nil-receiver` for a
|
|
464
|
+
# group that always matches.
|
|
425
465
|
def flatten_nominal_element(element, depth)
|
|
426
466
|
return element if depth.zero?
|
|
427
|
-
return element unless array_nominal?(element)
|
|
428
467
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
468
|
+
case element
|
|
469
|
+
when Type::Tuple
|
|
470
|
+
Type::Combinator.union(*element.elements.map { |inner| flatten_nominal_element(inner, depth - 1) })
|
|
471
|
+
when Type::Union
|
|
472
|
+
Type::Combinator.union(*element.members.map do |member|
|
|
473
|
+
member.is_a?(Type::Tuple) ? flatten_nominal_element(member, depth) : member
|
|
474
|
+
end)
|
|
475
|
+
else
|
|
476
|
+
array_nominal?(element) ? flatten_nominal_element(element.type_args.first, depth - 1) : element
|
|
477
|
+
end
|
|
433
478
|
end
|
|
434
479
|
|
|
435
480
|
def array_nominal?(type)
|
|
@@ -472,7 +517,7 @@ module Rigor
|
|
|
472
517
|
# leading sign. Base 10 is Ruby's decimal-literal grammar, so the result lifts to
|
|
473
518
|
# `decimal-int-string`. Signed ranges fall through entirely (the result could carry a `-` sign
|
|
474
519
|
# that `decimal-int-string` alone would need a sign proof for, matching the
|
|
475
|
-
# `Nominal[Integer]` tier's own signed-base-10 carve-out
|
|
520
|
+
# `Nominal[Integer]` tier's own signed-base-10 carve-out above).
|
|
476
521
|
#
|
|
477
522
|
# #1004 — every other base (a different literal, such as 8 or 16, or a value that is not
|
|
478
523
|
# statically known) does NOT lift to `octal-int-string` / `hex-int-string`: those refinements'
|
|
@@ -621,10 +666,10 @@ module Rigor
|
|
|
621
666
|
Type::Combinator.positive_int
|
|
622
667
|
end
|
|
623
668
|
|
|
624
|
-
# Predicate-subset projections over a `Refined[base, predicate]` receiver.
|
|
625
|
-
# the String case-normalisation pair: `s.downcase` over a
|
|
626
|
-
# the same carrier (already lowercase), and `s.upcase` lifts a
|
|
627
|
-
# `uppercase-string`. Symmetric rules apply with the predicates swapped. Numeric-string
|
|
669
|
+
# Predicate-subset projections over a `Refined[base, predicate]` receiver. The catalogue is
|
|
670
|
+
# `REFINED_STRING_PROJECTIONS`, led by the String case-normalisation pair: `s.downcase` over a
|
|
671
|
+
# `lowercase-string` receiver folds to the same carrier (already lowercase), and `s.upcase` lifts a
|
|
672
|
+
# `lowercase-string` to `uppercase-string`. Symmetric rules apply with the predicates swapped. Numeric-string
|
|
628
673
|
# idempotence over `#downcase` / `#upcase` is also recognised because a numeric string equals its
|
|
629
674
|
# own case-normalisation.
|
|
630
675
|
#
|
|
@@ -747,7 +792,7 @@ module Rigor
|
|
|
747
792
|
# Compute the bounded-integer meet of two or more `IntegerRange` carriers. We compare via the
|
|
748
793
|
# numeric `lower` / `upper` accessors (`-Float::INFINITY` / `Float::INFINITY` for the symbolic
|
|
749
794
|
# ends), then map back to the symbolic-bound representation `IntegerRange.new` expects. The
|
|
750
|
-
# disjoint-meet case cannot arise from sound member-wise projections
|
|
795
|
+
# disjoint-meet case cannot arise from sound member-wise projections but is guarded
|
|
751
796
|
# defensively to keep the carrier total.
|
|
752
797
|
def narrow_integer_ranges(ranges)
|
|
753
798
|
numeric_low = ranges.map(&:lower).max
|
|
@@ -1204,15 +1249,32 @@ module Rigor
|
|
|
1204
1249
|
Type::Combinator.tuple_of(*tuple.elements, *other.elements)
|
|
1205
1250
|
end
|
|
1206
1251
|
|
|
1207
|
-
# `tuple.compact` — removes every element that is `Constant[nil]`.
|
|
1208
|
-
#
|
|
1209
|
-
#
|
|
1252
|
+
# `tuple.compact` — removes every element that is `Constant[nil]`. When every element is a
|
|
1253
|
+
# `Constant` the nil set is decidable and the result is the exact Tuple of the kept elements.
|
|
1254
|
+
# Otherwise an element that may be nil leaves the arity unknown, so the result is `Array[E]` with
|
|
1255
|
+
# `E` the elements' union minus its `nil` constituent (#2519) — never a fixed-arity Tuple. When
|
|
1256
|
+
# no element carries a `nil` constituent there is nothing to strip and the method declines so
|
|
1257
|
+
# the RBS tier answers (an element such as `Dynamic[top]` may still be nil at runtime, so the
|
|
1258
|
+
# Tuple is not kept either).
|
|
1210
1259
|
def tuple_compact(tuple, _method_name, args)
|
|
1211
1260
|
return nil unless args.empty?
|
|
1212
|
-
return nil unless tuple.elements.all?(Type::Constant)
|
|
1213
1261
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1262
|
+
if tuple.elements.all?(Type::Constant)
|
|
1263
|
+
kept = tuple.elements.reject { |e| e.value.nil? }
|
|
1264
|
+
return Type::Combinator.tuple_of(*kept)
|
|
1265
|
+
end
|
|
1266
|
+
|
|
1267
|
+
tuple_compact_widened(tuple.elements)
|
|
1268
|
+
end
|
|
1269
|
+
|
|
1270
|
+
def tuple_compact_widened(elements)
|
|
1271
|
+
stripped = elements.map { |e| strip_nil_constituent(e) }
|
|
1272
|
+
return nil if stripped.zip(elements).all? { |s, e| s.equal?(e) }
|
|
1273
|
+
|
|
1274
|
+
element = Type::Combinator.union(*stripped)
|
|
1275
|
+
return Type::Combinator.tuple_of if element.is_a?(Type::Bot)
|
|
1276
|
+
|
|
1277
|
+
Type::Combinator.nominal_of("Array", type_args: [element])
|
|
1216
1278
|
end
|
|
1217
1279
|
|
|
1218
1280
|
# `uniq` (no block) → `Tuple` of the first occurrence of each distinct value. Folds only when
|
|
@@ -1293,7 +1355,8 @@ module Rigor
|
|
|
1293
1355
|
end
|
|
1294
1356
|
|
|
1295
1357
|
# `tuple.take(n)` — returns the first n elements as a new Tuple. The argument must be a
|
|
1296
|
-
# `Constant[Integer]`. n
|
|
1358
|
+
# `Constant[Integer]`. `n == 0` returns the empty Tuple, a negative `n` declines, and `n >= size`
|
|
1359
|
+
# returns the full receiver.
|
|
1297
1360
|
def tuple_take(tuple, _method_name, args)
|
|
1298
1361
|
n = non_negative_count_arg(args)
|
|
1299
1362
|
return nil if n.nil?
|
|
@@ -1919,7 +1982,7 @@ module Rigor
|
|
|
1919
1982
|
# `shape.values_at(:a, :b, ...)` with a list of static keys. Returns a `Tuple` whose per-position
|
|
1920
1983
|
# values are the per-key value types (`Constant[nil]` for missing keys, mirroring Ruby's runtime
|
|
1921
1984
|
# behaviour). A computed key takes the nil-bearing step {#hash_dig_step} gives it; the call falls
|
|
1922
|
-
# through when any step does (a computed key on an open shape).
|
|
1985
|
+
# through when any step does (a computed key on an open or empty shape).
|
|
1923
1986
|
def hash_values_at(shape, _method_name, args)
|
|
1924
1987
|
return nil if args.empty?
|
|
1925
1988
|
|
|
@@ -20,7 +20,6 @@ module Rigor
|
|
|
20
20
|
#
|
|
21
21
|
# Centralising both here keeps the receiver-shape and constant-unwrap knowledge in one place: if
|
|
22
22
|
# `Type::Singleton` or `Type::Constant` ever changes shape, there is a single edit rather than nine.
|
|
23
|
-
# The per-tier fold bodies stay where they are — only the shared gate moves.
|
|
24
23
|
module SingletonFolding
|
|
25
24
|
module_function
|
|
26
25
|
|