rigortype 0.4.6 → 0.4.8
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 +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- 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 +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -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 +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- 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 +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- 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.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- 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 +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- 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 +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- 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/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- 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 +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -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 +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- 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 +26 -18
- 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 +29 -10
- 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 +92 -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 +2 -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 +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- 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 +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -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/hkt_registry_holder.rb +6 -0
- 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_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- 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 +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -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/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- 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 +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -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/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +81 -5
- 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/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- 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 +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- 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/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -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 +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- 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 +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- 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/singleton_type_params.rb +104 -0
- 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 +123 -25
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.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/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +287 -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/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- 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 +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- 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/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- 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 +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- 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 +48 -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/mutation_cache.rb +4 -1
- 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 +81 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +32 -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 +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- 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 +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- 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/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -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 +238 -37
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- 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 +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- 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/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- 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 +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- 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 +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- 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 +11 -14
- 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 +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- 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 +8 -7
- 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-graphql/lib/rigor/plugin/graphql.rb +12 -2
- 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 +8 -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 +200 -4
- 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 +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- 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 +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
- 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/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- 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/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- 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/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- 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/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +82 -3
|
@@ -54,6 +54,114 @@ module Rigor
|
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
# Issue #2681 — which declared type, if any, one RBS overload hands a `def`'s parameter slot when the def's shape
|
|
58
|
+
# need not match the declaration's (an override bound from its ancestor, or a source `def` whose RBS was written
|
|
59
|
+
# for another shape). A slot takes a type only where the two shapes line up:
|
|
60
|
+
#
|
|
61
|
+
# - a leading positional (required or optional) takes the declared leading positional at the same ABSOLUTE
|
|
62
|
+
# position, and a post positional the declared trailing one, provided both sides have as many post as
|
|
63
|
+
# trailing positionals: Ruby fills the leading parameters left to right on both sides, so the argument a call
|
|
64
|
+
# puts at a leading position reaches the slot at the same one;
|
|
65
|
+
# - `*rest` takes the declared rest only when the def has exactly the declared leading positionals;
|
|
66
|
+
# - a keyword takes the declared keyword of its name, and `**kw` the declared keyword rest only when the def
|
|
67
|
+
# names every declared keyword.
|
|
68
|
+
#
|
|
69
|
+
# Any other overlap is {MISALIGNED}, and leaves the slot `Dynamic[Top]`. {NOT_PASSED} is an overload that passes
|
|
70
|
+
# the slot nothing (`def first(n)` under `Array#first`'s `()`), which the binder skips.
|
|
71
|
+
module SlotAlignment
|
|
72
|
+
module_function
|
|
73
|
+
|
|
74
|
+
MISALIGNED = :misaligned
|
|
75
|
+
NOT_PASSED = :not_passed
|
|
76
|
+
|
|
77
|
+
# `accepts_keywords` is false for a def with no keyword parameter and no `**`, `**nil` or `...`: Ruby passes a
|
|
78
|
+
# call's keywords to such a def as a Hash in one more positional argument.
|
|
79
|
+
Shape = Data.define(:requireds, :leading, :posts, :keywords, :accepts_keywords)
|
|
80
|
+
|
|
81
|
+
def shape_of(params_node)
|
|
82
|
+
return Shape.new(0, 0, 0, [].freeze, false) if params_node.nil?
|
|
83
|
+
|
|
84
|
+
requireds = params_node.requireds.size
|
|
85
|
+
keywords = params_node.keywords.map(&:name).freeze
|
|
86
|
+
accepts_keywords = !keywords.empty? || !params_node.keyword_rest.nil?
|
|
87
|
+
Shape.new(requireds, requireds + params_node.optionals.size, params_node.posts.size, keywords,
|
|
88
|
+
accepts_keywords)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The RBS type the overload `function` declares for `slot`, {MISALIGNED} or {NOT_PASSED}.
|
|
92
|
+
def declared(function, slot, shape)
|
|
93
|
+
return MISALIGNED if keyword_hash_reaches?(function, slot, shape)
|
|
94
|
+
|
|
95
|
+
case slot.kind
|
|
96
|
+
when :required_positional then leading(function, shape, slot.index)
|
|
97
|
+
when :optional_positional then leading(function, shape, shape.requireds + slot.index)
|
|
98
|
+
when :trailing_positional then trailing(function, shape, slot.index)
|
|
99
|
+
when :rest_positional then rest(function, shape)
|
|
100
|
+
when :required_keyword, :optional_keyword then keyword(function, slot.name)
|
|
101
|
+
when :rest_keyword then rest_keyword(function, shape)
|
|
102
|
+
else NOT_PASSED
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Whether the overload's keywords may reach `slot` as a positional Hash: the def accepts no keywords, so a
|
|
107
|
+
# call's keywords arrive as one more positional argument, which lands past the declared required leading
|
|
108
|
+
# positionals, in the `*rest`, or (shifting every post positional) in a post positional.
|
|
109
|
+
def keyword_hash_reaches?(function, slot, shape)
|
|
110
|
+
return false if shape.accepts_keywords
|
|
111
|
+
return false if function.required_keywords.empty? && function.optional_keywords.empty? &&
|
|
112
|
+
function.rest_keywords.nil?
|
|
113
|
+
|
|
114
|
+
case slot.kind
|
|
115
|
+
when :required_positional then slot.index >= function.required_positionals.size
|
|
116
|
+
when :optional_positional then shape.requireds + slot.index >= function.required_positionals.size
|
|
117
|
+
when :rest_positional, :trailing_positional then true
|
|
118
|
+
else false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def leading(function, shape, position)
|
|
123
|
+
return MISALIGNED unless function.trailing_positionals.size == shape.posts
|
|
124
|
+
|
|
125
|
+
required = function.required_positionals
|
|
126
|
+
return required[position].type if position < required.size
|
|
127
|
+
|
|
128
|
+
optional = function.optional_positionals[position - required.size]
|
|
129
|
+
return optional.type if optional
|
|
130
|
+
|
|
131
|
+
function.rest_positionals ? MISALIGNED : NOT_PASSED
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def trailing(function, shape, index)
|
|
135
|
+
return MISALIGNED unless function.trailing_positionals.size == shape.posts
|
|
136
|
+
|
|
137
|
+
function.trailing_positionals[index].type
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def rest(function, shape)
|
|
141
|
+
return MISALIGNED unless function.trailing_positionals.size == shape.posts
|
|
142
|
+
|
|
143
|
+
declared_leading = function.required_positionals.size + function.optional_positionals.size
|
|
144
|
+
return MISALIGNED if shape.leading < declared_leading
|
|
145
|
+
return NOT_PASSED if function.rest_positionals.nil?
|
|
146
|
+
|
|
147
|
+
shape.leading == declared_leading ? function.rest_positionals.type : MISALIGNED
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def keyword(function, name)
|
|
151
|
+
declared = function.required_keywords[name] || function.optional_keywords[name]
|
|
152
|
+
return declared.type if declared
|
|
153
|
+
|
|
154
|
+
function.rest_keywords ? MISALIGNED : NOT_PASSED
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def rest_keyword(function, shape)
|
|
158
|
+
declared = function.required_keywords.keys + function.optional_keywords.keys
|
|
159
|
+
return MISALIGNED unless declared.all? { |name| shape.keywords.include?(name) }
|
|
160
|
+
|
|
161
|
+
function.rest_keywords ? function.rest_keywords.type : NOT_PASSED
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
57
165
|
class MethodParameterBinder
|
|
58
166
|
# @param class_path — the qualified name of the class the method is defined in
|
|
59
167
|
# (e.g., `"Foo::Bar"`), or `nil` for a top-level `def` outside any class. When `nil` (or
|
|
@@ -64,11 +172,15 @@ module Rigor
|
|
|
64
172
|
# @param source_path — the project-relative path of the file the method is
|
|
65
173
|
# defined in. Used to match ADR-28 path-scoped protocol contracts; `nil` (the default
|
|
66
174
|
# for synthetic / probe scopes) disables the contract tier.
|
|
67
|
-
def
|
|
175
|
+
# @param scope — the scope the `def` is written in. When supplied, a `def` on a Ruby-source class with no RBS
|
|
176
|
+
# of its own binds the parameters of the declaration it overrides on an ADR-43 allow-listed ancestor (#2681);
|
|
177
|
+
# `nil` keeps that class's parameters `Dynamic[Top]`.
|
|
178
|
+
def initialize(environment:, class_path:, singleton:, source_path: nil, scope: nil)
|
|
68
179
|
@environment = environment
|
|
69
180
|
@class_path = class_path
|
|
70
181
|
@singleton = singleton
|
|
71
182
|
@source_path = source_path
|
|
183
|
+
@scope = scope
|
|
72
184
|
end
|
|
73
185
|
|
|
74
186
|
# @return ordered map from parameter name to bound type.
|
|
@@ -79,7 +191,9 @@ module Rigor
|
|
|
79
191
|
|
|
80
192
|
rbs_method = lookup_rbs_method(def_node)
|
|
81
193
|
if rbs_method
|
|
82
|
-
|
|
194
|
+
unless rbs_method.method_types.empty?
|
|
195
|
+
apply_rbs_overloads(types, slots, rbs_method.method_types, SlotAlignment.shape_of(def_node.parameters))
|
|
196
|
+
end
|
|
83
197
|
# `rigor:v1:param: <name> <refinement>` annotations tighten the bound type for matching
|
|
84
198
|
# slots. Applied after the RBS-overload pass so the override is the authoritative
|
|
85
199
|
# answer regardless of what the RBS signature declared.
|
|
@@ -186,15 +300,17 @@ module Rigor
|
|
|
186
300
|
# `def self.foo` always means a singleton method on the immediate enclosing class. `def
|
|
187
301
|
# foo` inside `class << self` is also a singleton method (the StatementEvaluator threads
|
|
188
302
|
# the `singleton:` flag through this case).
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
303
|
+
# ADR-43 (#2681) — a source-only subclass of an allow-listed RBS-complete ancestor (`Rigor::Plugin::Base`)
|
|
304
|
+
# overriding one of its hooks binds the hook's declared parameters, as an empty RBS declaration of the
|
|
305
|
+
# subclass would through RBS's own inheritance.
|
|
306
|
+
kind = def_node.receiver.is_a?(Prism::SelfNode) || @singleton ? :singleton : :instance
|
|
307
|
+
MethodDispatcher::RbsDispatch.def_declaration(@environment, @class_path, kind, method_name, @scope)
|
|
194
308
|
end
|
|
195
309
|
|
|
196
310
|
# Bind each parameter slot to the union of the matching parameter types across every
|
|
197
|
-
# overload that *has* that slot
|
|
311
|
+
# overload that *has* that slot, where the def's parameter shape lines up with the overload's
|
|
312
|
+
# ({SlotAlignment}); one overload that does not line up leaves the slot `Dynamic[Top]`.
|
|
313
|
+
# Overloads that pass the slot no argument (e.g., `Array#first` has
|
|
198
314
|
# both `()` and `(int)` overloads — only the second matches a `def first(n)` redefinition)
|
|
199
315
|
# are silently skipped, so the binder defaults to the most informative type the RBS
|
|
200
316
|
# signature provides without having to know which overload the runtime will pick.
|
|
@@ -203,14 +319,14 @@ module Rigor
|
|
|
203
319
|
# accepts any argument in every slot, so no slot can be narrowed below `Dynamic[Top]`: binding a typed
|
|
204
320
|
# sibling overload's parameter type would read a value that overload legitimately passes as a
|
|
205
321
|
# contradiction inside the body. Every slot keeps its default; the declared return type still applies.
|
|
206
|
-
def apply_rbs_overloads(types, slots, method_types)
|
|
322
|
+
def apply_rbs_overloads(types, slots, method_types, shape)
|
|
207
323
|
return unless method_types.all? { |mt| mt.type.respond_to?(:required_positionals) }
|
|
208
324
|
|
|
209
325
|
slots.each do |slot|
|
|
210
326
|
next if slot.name.nil?
|
|
211
327
|
|
|
212
|
-
translated = collect_translated_types(method_types, slot)
|
|
213
|
-
next if translated.empty?
|
|
328
|
+
translated = collect_translated_types(method_types, slot, shape)
|
|
329
|
+
next if translated.nil? || translated.empty?
|
|
214
330
|
|
|
215
331
|
types[slot.name] = build_slot_type(translated, slot.kind)
|
|
216
332
|
end
|
|
@@ -272,10 +388,15 @@ module Rigor
|
|
|
272
388
|
slots.find { |slot| POSITIONAL_KINDS.include?(slot.kind) && slot.index == index }
|
|
273
389
|
end
|
|
274
390
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
391
|
+
# The translated types the overloads declare for `slot`, or nil when one of them does not line up with the
|
|
392
|
+
# def's parameter shape ({SlotAlignment}): that overload may hand the slot an argument of another type.
|
|
393
|
+
def collect_translated_types(method_types, slot, shape)
|
|
394
|
+
rbs_types = []
|
|
395
|
+
method_types.each do |mt|
|
|
396
|
+
declared = SlotAlignment.declared(mt.type, slot, shape)
|
|
397
|
+
return nil if declared.equal?(SlotAlignment::MISALIGNED)
|
|
398
|
+
|
|
399
|
+
rbs_types << declared unless declared.equal?(SlotAlignment::NOT_PASSED)
|
|
279
400
|
end
|
|
280
401
|
rbs_types.map { |t| translate_with_self(t) }.uniq
|
|
281
402
|
end
|
|
@@ -285,47 +406,6 @@ module Rigor
|
|
|
285
406
|
wrap_for_kind(bound, kind)
|
|
286
407
|
end
|
|
287
408
|
|
|
288
|
-
# Dispatch table from slot kind to a small lambda that pulls the matching RBS parameter
|
|
289
|
-
# type out of an `RBS::Types::Function`. The hash keeps `rbs_type_for_slot` linear (one
|
|
290
|
-
# lookup, one call) so the cyclomatic-complexity budget does not balloon as future slices
|
|
291
|
-
# add more parameter kinds (e.g., `**Symbol kw` is a candidate for a stricter route in
|
|
292
|
-
# Slice 5+).
|
|
293
|
-
# Match keyword parameters by name across both required and optional keyword maps. RBS may
|
|
294
|
-
# declare a keyword as optional (`?by:`) while the Ruby `def` lists it as required (or
|
|
295
|
-
# vice versa); the binding is by-name regardless of which side defines it.
|
|
296
|
-
KEYWORD_PROVIDER = Ractor.make_shareable(lambda do |fn, slot|
|
|
297
|
-
fn.required_keywords[slot.name]&.type || fn.optional_keywords[slot.name]&.type
|
|
298
|
-
end)
|
|
299
|
-
private_constant :KEYWORD_PROVIDER
|
|
300
|
-
|
|
301
|
-
RBS_TYPE_PROVIDERS = Ractor.make_shareable({
|
|
302
|
-
required_positional: Ractor.make_shareable(lambda { |fn, slot|
|
|
303
|
-
fn.required_positionals[slot.index]&.type
|
|
304
|
-
}),
|
|
305
|
-
optional_positional: Ractor.make_shareable(lambda { |fn, slot|
|
|
306
|
-
fn.optional_positionals[slot.index]&.type
|
|
307
|
-
}),
|
|
308
|
-
rest_positional: Ractor.make_shareable(lambda { |fn, _slot|
|
|
309
|
-
fn.rest_positionals&.type
|
|
310
|
-
}),
|
|
311
|
-
trailing_positional: Ractor.make_shareable(lambda { |fn, slot|
|
|
312
|
-
fn.trailing_positionals[slot.index]&.type
|
|
313
|
-
}),
|
|
314
|
-
required_keyword: KEYWORD_PROVIDER,
|
|
315
|
-
optional_keyword: KEYWORD_PROVIDER,
|
|
316
|
-
rest_keyword: Ractor.make_shareable(lambda { |fn, _slot|
|
|
317
|
-
fn.rest_keywords&.type
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
private_constant :RBS_TYPE_PROVIDERS
|
|
321
|
-
|
|
322
|
-
def rbs_type_for_slot(function, slot)
|
|
323
|
-
provider = RBS_TYPE_PROVIDERS[slot.kind]
|
|
324
|
-
return nil unless provider
|
|
325
|
-
|
|
326
|
-
provider.call(function, slot)
|
|
327
|
-
end
|
|
328
|
-
|
|
329
409
|
# The variable bound to a `*rest` parameter is the *Array* of rest-positional arguments,
|
|
330
410
|
# not a single element. Likewise `**kw` is bound to a `Hash[Symbol, V]`. Wrap the
|
|
331
411
|
# translated element/value type accordingly so `rest` reads as `Array[Integer]` rather
|
|
@@ -41,8 +41,8 @@ module Rigor
|
|
|
41
41
|
any = arguments.empty?
|
|
42
42
|
arguments.each do |arg|
|
|
43
43
|
if arg.is_a?(Prism::DefNode)
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
ranges << [arg.location.start_offset, arg.location.end_offset, arg.name,
|
|
45
|
+
*ModuleFunctionState.def_row_side(arg, qualified_prefix, in_singleton_class, owner)]
|
|
46
46
|
elsif (name = Source::Literals.symbol_or_string(arg))
|
|
47
47
|
ranges << [*call_span, name, :singleton, owner]
|
|
48
48
|
else
|
|
@@ -54,6 +54,17 @@ module Rigor
|
|
|
54
54
|
module ModuleFunctionState
|
|
55
55
|
module_function
|
|
56
56
|
|
|
57
|
+
# The `[kind, owner]` of a deferred-range row for a `def` passed to a `module_function` spelling: `:singleton`
|
|
58
|
+
# for a singleton def, `:both` for an instance one, and `[nil, nil]` for a def on another object's singleton
|
|
59
|
+
# (#3155), which claims no owner or side.
|
|
60
|
+
def def_row_side(def_node, qualified_prefix, in_singleton_class, owner)
|
|
61
|
+
case ScopeIndexer.def_side(def_node, qualified_prefix, in_singleton_class)
|
|
62
|
+
when :singleton then [:singleton, owner]
|
|
63
|
+
when :instance then [:both, owner]
|
|
64
|
+
else [nil, nil]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
57
68
|
# A receiverless `module_function` call, bare or named.
|
|
58
69
|
def call?(node)
|
|
59
70
|
node.name == :module_function && node.receiver.nil?
|
|
@@ -76,6 +87,17 @@ module Rigor
|
|
|
76
87
|
(node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
|
|
77
88
|
end
|
|
78
89
|
|
|
90
|
+
# The `def` nodes a receiverless `public`, `private` or `protected` call takes as arguments (`private def x`,
|
|
91
|
+
# #2254). Ruby evaluates each one before the call, so it runs whenever the call's statement does. A named call
|
|
92
|
+
# with a symbol takes no def, and a call with a receiver is not read. A `module_function def` is not read here:
|
|
93
|
+
# its singleton copy is not modelled, so that gap stays as it was.
|
|
94
|
+
def visibility_call_defs(node)
|
|
95
|
+
return [] unless node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
96
|
+
return [] unless %i[public private protected].include?(node.name)
|
|
97
|
+
|
|
98
|
+
node.arguments&.arguments&.grep(Prism::DefNode) || []
|
|
99
|
+
end
|
|
100
|
+
|
|
79
101
|
# The mode after `stmt`, a direct statement of a body, for the readers that walk the statements: `mode`
|
|
80
102
|
# is `true`, `false` or `:uncertain` before it. A {.visibility_reset?} the statement runs ends the mode,
|
|
81
103
|
# and one it may not run (`:maybe_off`, below {.prescan_deferred}) makes an on mode uncertain. A
|
|
@@ -342,7 +364,6 @@ module Rigor
|
|
|
342
364
|
true
|
|
343
365
|
end
|
|
344
366
|
|
|
345
|
-
# The prescan's call arm.
|
|
346
367
|
def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
347
368
|
if bare?(node)
|
|
348
369
|
offsets << [node.location.start_offset, :on]
|
|
@@ -350,8 +371,8 @@ module Rigor
|
|
|
350
371
|
owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
|
|
351
372
|
node.arguments&.arguments&.each do |arg|
|
|
352
373
|
if arg.is_a?(Prism::DefNode)
|
|
353
|
-
|
|
354
|
-
|
|
374
|
+
ranges << [arg.location.start_offset, arg.location.end_offset, arg.name,
|
|
375
|
+
*ModuleFunctionState.def_row_side(arg, qualified_prefix, in_singleton_class, owner)]
|
|
355
376
|
elsif (name = symbol_argument_name(arg))
|
|
356
377
|
ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
|
|
357
378
|
else
|
|
@@ -12,7 +12,7 @@ module Rigor
|
|
|
12
12
|
#
|
|
13
13
|
# `Rigor::Inference::MultiTargetBinder` decomposes a tuple- or array-shaped right-hand side type
|
|
14
14
|
# against a Prism multi-target tree and produces a `name -> Rigor::Type` binding map. The
|
|
15
|
-
# binder is shared between
|
|
15
|
+
# binder is shared between five surfaces:
|
|
16
16
|
#
|
|
17
17
|
# 1. `Rigor::Inference::StatementEvaluator#eval_multi_write` for the statement-level `a, b =
|
|
18
18
|
# rhs` form (`Prism::MultiWriteNode`).
|
|
@@ -302,7 +302,7 @@ module Rigor
|
|
|
302
302
|
# and a per-slot `T | nil` fires `call.possible-nil-receiver` on the guarded read.
|
|
303
303
|
def join_member_bindings(types, soften_slots)
|
|
304
304
|
untyped = Type::Combinator.untyped
|
|
305
|
-
return [untyped, false] if types.any? { |type| type
|
|
305
|
+
return [untyped, false] if types.any? { |type| Type::Combinator.untyped?(type) }
|
|
306
306
|
|
|
307
307
|
firm = types.reject { |type| nil_literal?(type) }
|
|
308
308
|
return [Type::Combinator.union(*types), false] if !soften_slots || firm.empty? || firm.size == types.size
|
|
@@ -31,7 +31,12 @@ module Rigor
|
|
|
31
31
|
# `nil` when the re-join changed nothing, so a non-adder (`pop`, `sort!`) on an already-widened
|
|
32
32
|
# carrier does not rebind the binding at all: a rebind drops the per-slot narrowings `with_local`
|
|
33
33
|
# keys to the old value, and paying that for an identical type is a straight loss.
|
|
34
|
-
def widen_nominal(nominal, method_name, values: :widen, arg_types: MutationWidening::NO_ARG_TYPES
|
|
34
|
+
def widen_nominal(nominal, method_name, values: :widen, arg_types: MutationWidening::NO_ARG_TYPES,
|
|
35
|
+
synthesised: false)
|
|
36
|
+
if synthesised && (synthesised_carrier?(nominal) || partially_gradual_hash?(nominal))
|
|
37
|
+
return widen_synthesised(nominal, method_name, values:, arg_types:)
|
|
38
|
+
end
|
|
39
|
+
return widen_precise_array(nominal, method_name, values:, arg_types:) if precise_array?(nominal)
|
|
35
40
|
return nil unless regrowable_carrier?(nominal)
|
|
36
41
|
|
|
37
42
|
pre_state = values == :widen ? value_pin_widened_args(nominal) : nominal
|
|
@@ -115,19 +120,250 @@ module Rigor
|
|
|
115
120
|
# This is a superset of "mutation-widened": a DECLARED `Array[untyped]` or
|
|
116
121
|
# `Array[Integer | untyped]` matches too. Deliberate — the soundness argument above is about
|
|
117
122
|
# the carrier being open, not about who opened it, and a declared gradual arm is the author
|
|
118
|
-
# saying the collection may hold anything.
|
|
119
|
-
#
|
|
120
|
-
#
|
|
123
|
+
# saying the collection may hold anything.
|
|
124
|
+
#
|
|
125
|
+
# Issue #2555 L3 — the provenance this boundary turns on travels beside the binding, not with the value: a
|
|
126
|
+
# local whose object the engine built carries the `:synthesised` construction fact ({Scope#construction_fact?}),
|
|
127
|
+
# and {#widen_synthesised} answers for it before this predicate is asked. A field on `Type::Nominal` was the
|
|
128
|
+
# other route; `Combinator.union`'s structural dedupe would keep whichever tagged twin it met first.
|
|
121
129
|
def regrowable_carrier?(nominal)
|
|
122
130
|
return false unless %w[Array Hash].include?(nominal.class_name)
|
|
123
131
|
|
|
124
132
|
nominal.type_args.any? { |arg| gradual_parameter?(arg) }
|
|
125
133
|
end
|
|
126
134
|
|
|
135
|
+
# Issue #2555 L3 — a PRECISE `Array` / `Hash` nominal (no parameter carries a `Dynamic` arm), the carrier
|
|
136
|
+
# {#widen_synthesised} re-grows when its binding carries the `:synthesised` fact. A carrier that already carries
|
|
137
|
+
# a gradual arm keeps the ordinary re-join, whose floor it holds already.
|
|
138
|
+
def synthesised_carrier?(nominal)
|
|
139
|
+
%w[Array Hash].include?(nominal.class_name) && !nominal.type_args.empty? &&
|
|
140
|
+
nominal.type_args.none? { |arg| gradual_parameter?(arg) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Issue #2555 L3 — a `Hash` with one gradual parameter and one precise one: `Hash.new(0)` is
|
|
144
|
+
# `Hash[Dynamic[top], Integer]`. On a synthesised binding the precise side is this engine's own fold of the
|
|
145
|
+
# default, not a declaration, so {#keep_precise_parameters} must not restore it over a store:
|
|
146
|
+
# `h = Hash.new(0); h[:y] = "s"` reads `Hash[Dynamic[top] | Symbol, Integer | String]`, not
|
|
147
|
+
# `Hash[Dynamic[top] | Symbol, Integer]` (which made `h[:y].upcase` an `undefined method` on Integer). The
|
|
148
|
+
# counter idiom keeps its `Integer`: `h[:x] += 1` stores an `Integer`.
|
|
149
|
+
def partially_gradual_hash?(nominal)
|
|
150
|
+
nominal.class_name == "Hash" && nominal.type_args.size == 2 &&
|
|
151
|
+
nominal.type_args.any? { |arg| gradual_parameter?(arg) } &&
|
|
152
|
+
nominal.type_args.any? { |arg| !gradual_parameter?(arg) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Issue #2555 L3 — the re-join for a precise carrier the engine built itself: a collection literal closed by a
|
|
156
|
+
# value-preserving mutator (`a = [1, 2]; a.sort!` is `Array[1 | 2]`, `g = { a: 1 }; g.delete(:b)` is
|
|
157
|
+
# `Hash[Symbol, 1]`), a constructor fold (`Array.new(n, 0)` is `Array[Integer]`), or a literal built as a
|
|
158
|
+
# nominal outright (`{ ENV["A"].to_s => 1 }`). Its element types are this engine's inference, not a claim a
|
|
159
|
+
# declaration made, so the #561 boundary ({#regrowable_carrier?}, {#precise_admitted}) does not apply:
|
|
160
|
+
#
|
|
161
|
+
# - an added member joins as itself (value pin and literal shape dropped, {MutationWidening.value_pin_widened}),
|
|
162
|
+
# with no seed-admissibility gate and no `Dynamic[top]` floor — `Array.new(n, 0) << "s"` reads
|
|
163
|
+
# `Array[Integer | String]`;
|
|
164
|
+
# - a rewriter takes {RewriteMutation.arm}'s gradual position, as a gradual carrier does —
|
|
165
|
+
# `g.transform_values!(&:to_s)` reads `Hash[Symbol, Dynamic[top]]`;
|
|
166
|
+
# - an adder whose arguments yield no evidence is one unknown store and joins `Dynamic[top]`; one called with no
|
|
167
|
+
# argument types at all (the block-capture seam, whose content join adds the stores afterwards) changes
|
|
168
|
+
# nothing.
|
|
169
|
+
#
|
|
170
|
+
# The floor the ordinary re-join adds exists because a carrier it closed could not re-grow; this one stays
|
|
171
|
+
# re-growable while the binding keeps the fact, so every later store made THROUGH THE BINDING is seen. A store
|
|
172
|
+
# through another holder is not, which is why a local that may escape anywhere in its body never carries the
|
|
173
|
+
# fact ({ConstructionEscape}). Nor is a remover taking a store out again, which is why a remover on a carrier a
|
|
174
|
+
# store has grown restores the floor ({MutationWidening.floor_grown_carrier}).
|
|
175
|
+
def widen_synthesised(nominal, method_name, values:, arg_types:)
|
|
176
|
+
pre_state = values == :widen ? value_pin_widened_args(nominal) : nominal
|
|
177
|
+
rejoined =
|
|
178
|
+
case nominal.class_name
|
|
179
|
+
when "Array"
|
|
180
|
+
return nil unless MutationWidening::ARRAY_MUTATORS.include?(method_name)
|
|
181
|
+
|
|
182
|
+
synthesised_array_join(RewriteMutation.arm(pre_state, method_name), method_name, arg_types)
|
|
183
|
+
when "Hash"
|
|
184
|
+
return nil unless MutationWidening::HASH_MUTATORS.include?(method_name)
|
|
185
|
+
|
|
186
|
+
synthesised_hash_join(RewriteMutation.arm(pre_state, method_name), method_name, arg_types)
|
|
187
|
+
end
|
|
188
|
+
rejoined == nominal ? nil : rejoined
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Issue #2555 — the removers and filters of an Array, a Hash or a Set: each can take a stored member out again,
|
|
192
|
+
# which nothing narrows the carrier back from.
|
|
193
|
+
SYNTHESISED_REMOVERS = %i[
|
|
194
|
+
pop shift slice! delete delete? delete_at delete_if reject! select! filter! keep_if compact! clear subtract
|
|
195
|
+
].to_set.freeze
|
|
196
|
+
|
|
197
|
+
# The adders whose store marks a synthesised binding `:grown`.
|
|
198
|
+
def content_adder?(method_name)
|
|
199
|
+
ContentJoin::CONTENT_ADDERS.include?(method_name) || SetMutation::CONTENT_ADDERS.include?(method_name)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Whether a store turned `current` into `widened` by changing an Array / Hash / Set parameter that carried no
|
|
203
|
+
# `Dynamic` arm before it: a member joined where no floor covered it, which a later remover could leave stale.
|
|
204
|
+
# A store the parameter already held, or one that joined beside an earlier floor (`Hash.new(0)`'s key side),
|
|
205
|
+
# grows nothing.
|
|
206
|
+
def grew_unfloored?(current, widened)
|
|
207
|
+
return false if widened.nil?
|
|
208
|
+
|
|
209
|
+
before = ContentJoin.union_members(current)
|
|
210
|
+
ContentJoin.union_members(widened).any? do |member|
|
|
211
|
+
next false unless member.is_a?(Type::Nominal) && %w[Array Hash Set].include?(member.class_name)
|
|
212
|
+
|
|
213
|
+
prior = before.find { |b| same_carrier?(b, member) }
|
|
214
|
+
member.type_args.each_with_index.any? do |arg, i|
|
|
215
|
+
prior.nil? || (!gradual_parameter?(prior.type_args[i]) && prior.type_args[i] != arg)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def same_carrier?(type, nominal)
|
|
221
|
+
type.is_a?(Type::Nominal) && type.class_name == nominal.class_name &&
|
|
222
|
+
type.type_args.size == nominal.type_args.size
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Whether `method_name` on the synthesised local `name` takes the floor: a remover on a carrier a store grew.
|
|
226
|
+
def floors_grown_carrier?(scope, name, method_name)
|
|
227
|
+
SYNTHESISED_REMOVERS.include?(method_name) && scope.construction_fact?(name, :grown)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# `type` with every Array / Hash / Set parameter joined with `Dynamic[top]`, member-wise through a union: the
|
|
231
|
+
# reading the carrier would have had without the fact. With `drop_nil` (a remover that can take a `nil` out,
|
|
232
|
+
# {MutationWidening.removes_nil?}) the parameter's `nil` member goes too: on the path without the fact a stored
|
|
233
|
+
# `nil` the seed did not accept joined as the floor alone ({#precise_admitted}), and a `nil` kept beside the floor
|
|
234
|
+
# would still draw `call.possible-nil-receiver` after `parts << nil; parts.compact!`. Any other remover keeps the
|
|
235
|
+
# `nil`, which it may leave behind (`s << nil; s.delete(1)`).
|
|
236
|
+
def floor_parameters(type, drop_nil: false)
|
|
237
|
+
case type
|
|
238
|
+
when Type::Union then Type::Combinator.union(*type.members.map { |member| floor_parameters(member, drop_nil:) })
|
|
239
|
+
when Type::Nominal
|
|
240
|
+
return type unless %w[Array Hash Set].include?(type.class_name) && !type.type_args.empty?
|
|
241
|
+
|
|
242
|
+
Type::Combinator.nominal_of(type.class_name,
|
|
243
|
+
type_args: type.type_args.map { |arg| floored_parameter(arg, drop_nil) })
|
|
244
|
+
else type
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def hash_carrier?(type)
|
|
249
|
+
ContentJoin.union_members(type).any? { |member| member.is_a?(Type::Nominal) && member.class_name == "Hash" }
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def floored_parameter(arg, drop_nil)
|
|
253
|
+
kept = ContentJoin.union_members(arg)
|
|
254
|
+
kept = kept.reject { |member| member.is_a?(Type::Constant) && member.value.nil? } if drop_nil
|
|
255
|
+
Type::Combinator.union(*kept, Type::Combinator.untyped)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def synthesised_array_join(carrier, method_name, arg_types)
|
|
259
|
+
return carrier unless ContentJoin::ARRAY_CONTENT_ADDERS.include?(method_name) && !arg_types.empty?
|
|
260
|
+
|
|
261
|
+
added = MutationWidening.value_pin_widened(ContentJoin.array_added_elements(method_name, arg_types))
|
|
262
|
+
ContentJoin.join_array_content(carrier, added.empty? ? [Type::Combinator.untyped] : added)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def synthesised_hash_join(carrier, method_name, arg_types)
|
|
266
|
+
return carrier unless ContentJoin::HASH_CONTENT_ADDERS.include?(method_name) && !arg_types.empty?
|
|
267
|
+
|
|
268
|
+
# A `nil` key marks a splat, an unknown key ({MutationWidening.join_added_pairs}).
|
|
269
|
+
key, value = MutationWidening.value_pin_widened([arg_types.first || Type::Combinator.untyped, arg_types.last])
|
|
270
|
+
value ||= Type::Combinator.untyped
|
|
271
|
+
ContentJoin.join_hash_content(carrier, [[key, value]])
|
|
272
|
+
end
|
|
273
|
+
|
|
127
274
|
def gradual_parameter?(type)
|
|
128
275
|
ContentJoin.union_members(type).any?(Type::Dynamic)
|
|
129
276
|
end
|
|
130
277
|
|
|
278
|
+
# A typed `Array[E]` whose `E` has no `Dynamic` arm — the carrier {#regrowable_carrier?} refuses.
|
|
279
|
+
def precise_array?(nominal)
|
|
280
|
+
nominal.class_name == "Array" && nominal.type_args.size == 1 && !gradual_parameter?(nominal.type_args.first)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Issue #2361 — a PRECISE `Array[E]` re-grows under a store, on the literal arms' terms: `a = ARGV.dup;
|
|
284
|
+
# a.clear; a << 1; a.each { |v| v.fdiv(2) }` read `Array[String]` and reported `undefined method 'fdiv' for
|
|
285
|
+
# String` on code that prints `[0.5]`.
|
|
286
|
+
#
|
|
287
|
+
# Only a content adder ({ContentJoin::ARRAY_CONTENT_ADDERS}) re-grows it, and the join keeps the seed: `clear`
|
|
288
|
+
# empties the array but leaves `E` standing, a superset of what it holds. A remover leaves the carrier as it
|
|
289
|
+
# found it. An element rewriter (`map!`, `collect!`, `flatten!`) replaces `E` (issue #2427): with the element
|
|
290
|
+
# the non-bang counterpart answers when the call site could type it ({RewriteMutation.typed}, answered before
|
|
291
|
+
# this method), and otherwise with {RewriteMutation.arm}'s gradual element — `a.map!.with_index { … }` holds
|
|
292
|
+
# whatever the block returns, and keeping the stale `E` reported a missing method on correct code.
|
|
293
|
+
#
|
|
294
|
+
# Each added member the element accepts joins as itself; any other takes the `Dynamic[top]` floor
|
|
295
|
+
# ({#precise_admitted}), and the floor joins every re-grow as it does on the literal arms. `a << 1` therefore
|
|
296
|
+
# reads `Array[Dynamic[top] | String]` and `s = ints.map { "x" }; s << "y"` reads `Array["x" | Dynamic[top]]`.
|
|
297
|
+
# Nothing narrows a carrier back when a remover or rewriter takes the store out again (`a << 1; a.pop`,
|
|
298
|
+
# `a << nil; a.compact!`, `a.map!(&:to_s)`), so a joined `Integer` or `nil` made `t.take(a)` against a declared
|
|
299
|
+
# `Array[String]` report `call.argument-type-mismatch`, and a parameter returned as its declared
|
|
300
|
+
# `-> Array[String]` report `def.return-type-mismatch`, on correct Ruby. A union carrying the floor neither folds
|
|
301
|
+
# nor dispatches loudly, so the read stays silent. Once floored, the carrier is gradual and later stores re-join
|
|
302
|
+
# through {#widen_nominal}'s ordinary arm.
|
|
303
|
+
#
|
|
304
|
+
# A store the element already accepts changes nothing ({#already_held?}). An adder with no argument types at
|
|
305
|
+
# all is left alone, as {MutationWidening.join_added_elements} leaves it: the block-capture seams pass none,
|
|
306
|
+
# and their own content join adds the stores.
|
|
307
|
+
def widen_precise_array(nominal, method_name, values:, arg_types:)
|
|
308
|
+
return RewriteMutation.arm(nominal, method_name) if RewriteMutation::TYPED_COUNTERPARTS.key?(method_name)
|
|
309
|
+
return nil unless ContentJoin::ARRAY_CONTENT_ADDERS.include?(method_name) && !arg_types.empty?
|
|
310
|
+
|
|
311
|
+
stored = ContentJoin.array_added_elements(method_name, arg_types)
|
|
312
|
+
return nil if already_held?(nominal.type_args.first, stored)
|
|
313
|
+
|
|
314
|
+
added = MutationWidening.value_pin_widened(stored)
|
|
315
|
+
admitted = precise_admitted(nominal.type_args.first, added)
|
|
316
|
+
pre_state = values == :widen ? value_pin_widened_args(nominal) : nominal
|
|
317
|
+
rejoined = ContentJoin.join_array_content(pre_state, MutationWidening.gradual_floor(admitted))
|
|
318
|
+
rejoined == nominal ? nil : rejoined
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# The seed-admissibility gate for a precise element: an added member joins as itself only when `element`
|
|
322
|
+
# already accepts it, and takes the `Dynamic[top]` floor otherwise. A precise element therefore never gains a
|
|
323
|
+
# member it rejects, which is what a declared boundary checks: nothing narrows the carrier back when a remover
|
|
324
|
+
# or rewriter takes the store out again, and a joined foreign arm stays rejected beside the floor — `Integer`
|
|
325
|
+
# under a declared `Array[String]`, `String` under `-> Array["x"]`, `Array[Integer]` under
|
|
326
|
+
# `Array[Array[String]]` each reported on correct Ruby. A class set ({ContentJoin.admissible_evidence}) is too
|
|
327
|
+
# coarse for that: it admits `Array[Integer]` beside `[String]` and `"y"` beside `"x"`.
|
|
328
|
+
def precise_admitted(element, added)
|
|
329
|
+
added.flat_map do |type|
|
|
330
|
+
ContentJoin.union_members(type).map do |member|
|
|
331
|
+
Acceptance.accepts(element, member).yes? ? member : Type::Combinator.untyped
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# True when the precise element `element` already covers every type `stored` puts in, so the store changes
|
|
337
|
+
# nothing: `s = ints.map { "x" }; s << "x"` keeps `Array["x"]` and its `s.last == "x"` fold, which is correct,
|
|
338
|
+
# and a `[Integer, Integer]` pushed onto an `Array[[0 | Integer, Integer]]` keeps the Tuple element. Joining
|
|
339
|
+
# the value-pin-widened, shape-erased store instead would only lose precision.
|
|
340
|
+
#
|
|
341
|
+
# A stored type carrying `Dynamic` anywhere is never covered, though `Acceptance` passes it gradually: keeping
|
|
342
|
+
# the element would claim the unknown part is what the element says (`retry_binding_accepted?`'s rule).
|
|
343
|
+
def already_held?(element, stored)
|
|
344
|
+
return false if stored.empty?
|
|
345
|
+
|
|
346
|
+
held = ContentJoin.union_members(element)
|
|
347
|
+
stored.all? do |type|
|
|
348
|
+
next false if type.nil? || carries_dynamic?(type)
|
|
349
|
+
|
|
350
|
+
ContentJoin.union_members(type).all? { |member| held.include?(member) } ||
|
|
351
|
+
Acceptance.accepts(element, type).yes?
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def carries_dynamic?(type)
|
|
356
|
+
case type
|
|
357
|
+
when Type::Dynamic then true
|
|
358
|
+
when Type::Union, Type::Intersection then type.members.any? { |member| carries_dynamic?(member) }
|
|
359
|
+
when Type::Nominal then type.type_args.any? { |arg| carries_dynamic?(arg) }
|
|
360
|
+
when Type::Tuple then type.elements.any? { |element| carries_dynamic?(element) }
|
|
361
|
+
when Type::HashShape then type.pairs.each_value.any? { |value| carries_dynamic?(value) }
|
|
362
|
+
when Type::Difference, Type::Refined then carries_dynamic?(type.base)
|
|
363
|
+
else false
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
131
367
|
# The value-rewriting counterpart of {MutationWidening.widen_tuple}'s `values: :widen`. A mutator that lands a
|
|
132
368
|
# new value in an EXISTING slot falsifies that slot's pinning on a re-opened carrier exactly as
|
|
133
369
|
# it does on a literal one, so `d = [:multi]; d << x; d[0] = "s"` must not keep reading `:multi`
|