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
|
@@ -20,8 +20,9 @@ module Rigor
|
|
|
20
20
|
# The rule is exact rather than heuristic — the receiver must be a `Singleton[C]` whose `C` the project
|
|
21
21
|
# pre-pass recorded as including `Singleton` — so a class that does not include the mixin is untouched,
|
|
22
22
|
# and a project that defines its own `Singleton` module is the one case that would misfire. Guarded by
|
|
23
|
-
# {
|
|
24
|
-
#
|
|
23
|
+
# {own_singleton_include?}: when the included name resolves to a project-declared class or module (a
|
|
24
|
+
# top-level `Singleton`, or `M::Singleton` inside `module M`), the tier declines, because then the name
|
|
25
|
+
# means something else entirely.
|
|
25
26
|
module SingletonMixinDispatch
|
|
26
27
|
MIXIN_NAMES = Ractor.make_shareable(Set["Singleton", "::Singleton"])
|
|
27
28
|
private_constant :MIXIN_NAMES
|
|
@@ -31,30 +32,62 @@ module Rigor
|
|
|
31
32
|
|
|
32
33
|
module_function
|
|
33
34
|
|
|
34
|
-
# @return `
|
|
35
|
+
# @return the instance type of `C` for `C.instance`, or nil to decline.
|
|
35
36
|
def try_dispatch(context)
|
|
36
|
-
return nil unless context.method_name == SELECTOR
|
|
37
|
-
return nil unless context.args.empty?
|
|
38
|
-
|
|
39
37
|
receiver = context.receiver
|
|
40
|
-
return nil unless receiver.
|
|
41
|
-
|
|
42
|
-
scope = context.scope
|
|
43
|
-
return nil if scope.nil?
|
|
44
|
-
return nil unless includes_singleton_mixin?(scope, receiver.class_name)
|
|
45
|
-
return nil if project_singleton_shadow?(scope)
|
|
38
|
+
return nil unless answers?(context.scope, receiver, context.method_name, context.args.empty?)
|
|
46
39
|
|
|
47
40
|
Type::Combinator.nominal_of(receiver.class_name)
|
|
48
41
|
end
|
|
49
42
|
|
|
43
|
+
# Issue #2438 — the one predicate behind {try_dispatch} and `CheckRules#undefined_method_diagnostic`, so the
|
|
44
|
+
# typer and the `call.undefined-method` rule cannot disagree about `C.instance`: a call this tier types is
|
|
45
|
+
# never reported as undefined, whatever a project sidecar of `C` lists.
|
|
46
|
+
def answers?(scope, receiver, method_name, no_arguments)
|
|
47
|
+
return false unless method_name == SELECTOR && no_arguments
|
|
48
|
+
return false unless receiver.is_a?(Type::Singleton)
|
|
49
|
+
return false if scope.nil?
|
|
50
|
+
|
|
51
|
+
includes_singleton_mixin?(scope, receiver.class_name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# True when `class_name`'s instance ancestry carries the stdlib `Singleton` as an `include` written in the
|
|
55
|
+
# class itself or in a project class up its superclass chain (Ruby's `Singleton.included` also installs an
|
|
56
|
+
# `inherited` hook, so `class D < C` answers `instance` with its own instance). Read off
|
|
57
|
+
# `Scope::ResolutionChain` (the `:constants` flavor, so an empty project `module Singleton` is a project
|
|
58
|
+
# entry), where the stdlib module is an external entry. Issue #2438 — what is not the mixin:
|
|
59
|
+
#
|
|
60
|
+
# - `prepend Singleton`, which runs `prepend_features` instead of the `included` hook and so defines no
|
|
61
|
+
# `instance`; a prepended entry sits ahead of its owner on the chain;
|
|
62
|
+
# - an `include Singleton` in a MODULE the class includes: `Singleton.included` raises `TypeError` for a
|
|
63
|
+
# module, so that code never runs;
|
|
64
|
+
# - a top-level project `Singleton`, which both `Singleton` and `::Singleton` name.
|
|
65
|
+
#
|
|
66
|
+
# A NESTED project `N::Singleton` the name resolved to is read as the stdlib mixin: the discovery tables
|
|
67
|
+
# drop the leading `::` of a mixin argument, so `include ::Singleton` and `include Singleton` inside
|
|
68
|
+
# `module N` record the same name, and the stdlib reading is the one that types the rooted spelling and
|
|
69
|
+
# never reports a correct call.
|
|
50
70
|
def includes_singleton_mixin?(scope, class_name)
|
|
51
|
-
|
|
71
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s.delete_prefix("::"), :instance, :constants)
|
|
72
|
+
chain.record(scope)
|
|
73
|
+
classes = chain.level_classes.compact
|
|
74
|
+
chain.entries.each_with_index.any? { |entry, index| singleton_include?(chain, classes, entry, index) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def singleton_include?(chain, classes, entry, index)
|
|
78
|
+
if entry.external?
|
|
79
|
+
!entry.superclass_edge && MIXIN_NAMES.include?(entry.raw.to_s) && classes.include?(entry.owner) &&
|
|
80
|
+
index > chain.index_of(entry.owner)
|
|
81
|
+
else
|
|
82
|
+
entry.name.end_with?("::Singleton") && included_by_level_class?(chain, index)
|
|
83
|
+
end
|
|
52
84
|
end
|
|
53
85
|
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
86
|
+
# The entry sits after its level's class, so it was included into that level rather than prepended.
|
|
87
|
+
def included_by_level_class?(chain, index)
|
|
88
|
+
level = chain.level_starts.rindex { |start| start <= index }
|
|
89
|
+
klass = level && chain.level_classes[level]
|
|
90
|
+
!klass.nil? && index > chain.index_of(klass)
|
|
58
91
|
end
|
|
59
92
|
end
|
|
60
93
|
end
|
|
@@ -14,8 +14,8 @@ module Rigor
|
|
|
14
14
|
#
|
|
15
15
|
# A `Struct` instance is mutable (`s.x = v`, `s[:x] = v`, escape), so a member map bound to a
|
|
16
16
|
# variable can be invalidated by a later write. A member read folds off a **fresh** instance (the
|
|
17
|
-
# transient receiver of a `.new(...).x`
|
|
18
|
-
#
|
|
17
|
+
# transient receiver of a `.new(...).x` chain, unmutatable between materialisation and the read —
|
|
18
|
+
# slices 1+2), off a **fold-safe stored local** the {Inference::StructFoldSafety} scan
|
|
19
19
|
# proved is never mutated / aliased / escaped (slice 3), or off a **setter-mutated fold-safe local**
|
|
20
20
|
# whose bindings {#apply_setter_writeback} keeps current (slice 4). Any other stored receiver degrades
|
|
21
21
|
# to `Dynamic[top]` rather than fold a possibly-stale value.
|
|
@@ -255,8 +255,8 @@ module Rigor
|
|
|
255
255
|
end
|
|
256
256
|
|
|
257
257
|
# A member read is foldable when the receiver is either FRESH (the transient result of a
|
|
258
|
-
# `.new(...)
|
|
259
|
-
#
|
|
258
|
+
# `.new(...)` chain, which cannot have been mutated between materialisation and this read) or a
|
|
259
|
+
# FOLD-SAFE stored local (ADR-48 slice 3 — `StructFoldSafety` proved the binding is
|
|
260
260
|
# never mutated / aliased / escaped in its scope).
|
|
261
261
|
def foldable_receiver?(context)
|
|
262
262
|
fresh_receiver?(context) || fold_safe_local_receiver?(context) ||
|
|
@@ -308,7 +308,7 @@ module Rigor
|
|
|
308
308
|
end
|
|
309
309
|
|
|
310
310
|
# A fresh receiver is the result of a chained call that MATERIALISED the struct — `Point.new(1, 2).x`,
|
|
311
|
-
# `Point[1, 2].x
|
|
311
|
+
# `Point[1, 2].x`. Not merely a chained call: slices 1-2 read "chained" as
|
|
312
312
|
# "cannot have been mutated between materialisation and the read", and a method that hands back its
|
|
313
313
|
# own receiver falsifies that (issue #595):
|
|
314
314
|
#
|
|
@@ -31,12 +31,10 @@ module Rigor
|
|
|
31
31
|
# nodes; the bound exists so a pathological body cannot turn a per-call-site gate into a walk.
|
|
32
32
|
FACTORY_BODY_SCAN_BUDGET = 200
|
|
33
33
|
|
|
34
|
-
# Issue #595 / #525 — the ONE materialisation test
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# one answer. A `.with` call is never a materialisation: the composed `x.dup_self.with(indent: 9)`
|
|
39
|
-
# shape once slipped through as one, and `Struct#with` no longer exists to fold (#1335).
|
|
34
|
+
# Issue #595 / #525 — the ONE materialisation test, shared by {StructFolding#fresh_receiver?} and
|
|
35
|
+
# `ExpressionTyper`'s `:self`-grant arm so the two gates cannot fork. A `.with` call is never a
|
|
36
|
+
# materialisation: the composed `x.dup_self.with(indent: 9)` shape once slipped through as one, and
|
|
37
|
+
# `Struct#with` no longer exists (#1335).
|
|
40
38
|
#
|
|
41
39
|
# @param node — the receiver EXPRESSION.
|
|
42
40
|
# @param scope — the scope the factory and ancestor lookups resolve against.
|
|
@@ -55,7 +55,7 @@ module Rigor
|
|
|
55
55
|
|
|
56
56
|
# A form long enough to suggest generated or accumulated data rather than a literal a reader wrote;
|
|
57
57
|
# past it the folded `Constant[String]` stops being useful in a diagnostic and only costs memory.
|
|
58
|
-
#
|
|
58
|
+
# Counts pairs where `ShapeDispatch`'s `tuple_join` fold caps bytes, for the same reason.
|
|
59
59
|
FORM_PAIR_LIMIT = 64
|
|
60
60
|
private_constant :FORM_PAIR_LIMIT
|
|
61
61
|
|
|
@@ -41,6 +41,7 @@ require_relative "method_dispatcher/universal_object_dispatch"
|
|
|
41
41
|
require_relative "method_dispatcher/singleton_mixin_dispatch"
|
|
42
42
|
require_relative "method_dispatcher/method_folding"
|
|
43
43
|
require_relative "method_dispatcher/optional_count"
|
|
44
|
+
require_relative "../run_scoped_slots"
|
|
44
45
|
|
|
45
46
|
module Rigor
|
|
46
47
|
module Inference
|
|
@@ -204,8 +205,8 @@ module Rigor
|
|
|
204
205
|
# user-authored RBS overrides substrate synthesis) and ABOVE the dependency-source
|
|
205
206
|
# inference tier so a plugin's declared emit table beats the generic gem-source fallback
|
|
206
207
|
# for the same class. Slice 6a-TierB (origin_module dispatch) lands precise return types
|
|
207
|
-
# for Tier B emissions; Tier C emissions
|
|
208
|
-
#
|
|
208
|
+
# for Tier B emissions; Tier C emissions return their manifest `return_type` when the environment
|
|
209
|
+
# names that class (slice 6b), and `Dynamic[T]` otherwise.
|
|
209
210
|
synthetic_result = try_synthetic_method(
|
|
210
211
|
receiver_type, method_name, arg_types, block_type, environment
|
|
211
212
|
)
|
|
@@ -915,8 +916,7 @@ module Rigor
|
|
|
915
916
|
result || EMPTY_CONTRIBUTIONS
|
|
916
917
|
end
|
|
917
918
|
|
|
918
|
-
# Runs the precision tiers
|
|
919
|
-
# order and returns the first non-nil answer. Each tier owns its own receiver/argument
|
|
919
|
+
# Runs the precision tiers in order and returns the first non-nil answer. Each tier owns its own receiver/argument
|
|
920
920
|
# shape checks; a tier that does not recognise the receiver returns nil so the next tier
|
|
921
921
|
# can try. The RBS tier sits below this chain and is invoked by the outer `dispatch`
|
|
922
922
|
# method.
|
|
@@ -925,7 +925,7 @@ module Rigor
|
|
|
925
925
|
# (`try_dispatch(CallContext) -> Type?`). Order is significant: ConstantFolding's
|
|
926
926
|
# exact-value folds win first, the stdlib singleton folders sit in the middle (each
|
|
927
927
|
# gates on a distinct `Singleton` receiver, so their relative order is immaterial), and
|
|
928
|
-
# BlockFolding runs
|
|
928
|
+
# BlockFolding runs near the end because its rules only apply to block-taking calls — the cheaper
|
|
929
929
|
# arity folds above it filter the common cases first. Adding a precise tier is a one-line
|
|
930
930
|
# append here rather than another link in a hand-written `||` ladder.
|
|
931
931
|
PRECISE_TIERS_HEAD = Ractor.make_shareable([
|
|
@@ -1031,6 +1031,7 @@ module Rigor
|
|
|
1031
1031
|
fallback_receiver = user_class_fallback_receiver(receiver_type, environment)
|
|
1032
1032
|
return nil if fallback_receiver.nil?
|
|
1033
1033
|
return nil if declared_by_external_ancestor?(receiver_type, environment, context)
|
|
1034
|
+
return nil if project_singleton_definer?(receiver_type, context)
|
|
1034
1035
|
|
|
1035
1036
|
# Preserve the ORIGINAL receiver type as the `self` substitution so `Kernel#dup: () ->
|
|
1036
1037
|
# self` and other `self`-returning methods route through Object's RBS while still
|
|
@@ -1078,7 +1079,54 @@ module Rigor
|
|
|
1078
1079
|
record_dependencies: false).nil?
|
|
1079
1080
|
end
|
|
1080
1081
|
|
|
1081
|
-
|
|
1082
|
+
# Issue #2284 — a class object whose singleton chain holds a project definer of the name (its own `def
|
|
1083
|
+
# self.name`, one a project superclass wrote, an `extend`ed module's `def name`) answers from that definer,
|
|
1084
|
+
# not from `Module#name` / `Class#…` on the `Singleton[Object]` fallback: Ruby's singleton ancestry puts
|
|
1085
|
+
# every project entry ahead of `Class`. The fallback declines so the typer's singleton-body inference
|
|
1086
|
+
# answers the definer's return, or `Dynamic` where the slot is contested and the def may not be the one
|
|
1087
|
+
# that runs.
|
|
1088
|
+
#
|
|
1089
|
+
# The chain walk files the class edge of every entry it passes, so it runs only for a name some project file
|
|
1090
|
+
# defines at all ({BlockCallTiming.project_defines_anywhere?}, which files the `defines:<name>` edge either way,
|
|
1091
|
+
# #1459). Without it every class-body `private` / `attr_reader` call on a project class would depend on its
|
|
1092
|
+
# whole ancestry. The by-name answer is memoised per discovery index; the edge is filed on every call.
|
|
1093
|
+
def project_singleton_definer?(receiver_type, context)
|
|
1094
|
+
return false unless receiver_type.is_a?(Type::Singleton)
|
|
1095
|
+
|
|
1096
|
+
scope = context.scope
|
|
1097
|
+
return false if scope.nil?
|
|
1098
|
+
return false unless project_defines_name?(scope, context.method_name)
|
|
1099
|
+
|
|
1100
|
+
!scope.singleton_def_through_ancestors(receiver_type.class_name, context.method_name).last.nil?
|
|
1101
|
+
end
|
|
1102
|
+
|
|
1103
|
+
def project_defines_name?(scope, method_name)
|
|
1104
|
+
memo = defines_name_memo(scope)
|
|
1105
|
+
if memo.key?(method_name)
|
|
1106
|
+
Analysis::DependencyRecorder.read_missing(:defines, method_name) if Analysis::DependencyRecorder.active?
|
|
1107
|
+
return memo[method_name]
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
memo[method_name] = BlockCallTiming.project_defines_anywhere?(method_name, scope)
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
DEFINES_NAME_MEMO_KEY = RunScopedSlots.register(:__rigor_user_class_fallback_defines_name__)
|
|
1114
|
+
private_constant :DEFINES_NAME_MEMO_KEY
|
|
1115
|
+
|
|
1116
|
+
# One slot per discovery index and environment, as {#external_ancestor_memo} keeps its own: the answer reads
|
|
1117
|
+
# only the frozen discovery tables and the environment's `pre_eval:` patches.
|
|
1118
|
+
def defines_name_memo(scope)
|
|
1119
|
+
discovery = scope.discovery
|
|
1120
|
+
environment = scope.environment
|
|
1121
|
+
slot = Thread.current[DEFINES_NAME_MEMO_KEY]
|
|
1122
|
+
unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
|
|
1123
|
+
slot = [discovery, environment, {}]
|
|
1124
|
+
Thread.current[DEFINES_NAME_MEMO_KEY] = slot
|
|
1125
|
+
end
|
|
1126
|
+
slot[2]
|
|
1127
|
+
end
|
|
1128
|
+
|
|
1129
|
+
EXTERNAL_ANCESTOR_MEMO_KEY = RunScopedSlots.register(:__rigor_user_class_fallback_external_ancestor__)
|
|
1082
1130
|
private_constant :EXTERNAL_ANCESTOR_MEMO_KEY
|
|
1083
1131
|
|
|
1084
1132
|
# One slot per discovery index and environment, as `RbsDispatch#ancestor_memo_slot` keeps its own: the answer
|
|
@@ -1138,12 +1186,14 @@ module Rigor
|
|
|
1138
1186
|
#
|
|
1139
1187
|
# `RbsDispatch` already distributes over a `Union` (and erases a `Difference` to its base, #533),
|
|
1140
1188
|
# but it does so INSIDE one tier, so a member that needs a LOWER tier — a user class with no RBS,
|
|
1141
|
-
# a discovered `def`, an opt-in gem — makes the whole union decline. That
|
|
1142
|
-
# rigor-actionpack
|
|
1143
|
-
#
|
|
1144
|
-
# `#to_s` read opaque on every Rails app. Measured by toggling this file alone: the
|
|
1145
|
-
# census
|
|
1146
|
-
# diagnostics on both arms across eight survey targets.
|
|
1189
|
+
# a discovered `def`, an opt-in gem — makes the whole union decline. That was the corpus shape before
|
|
1190
|
+
# #2430: rigor-actionpack typed `params[:k]` as the bare `ActionController::Parameters | nil` union, the
|
|
1191
|
+
# `Parameters` arm resolved only through the user-class fallback, and so `params[:k].present?` / `#==`
|
|
1192
|
+
# / `#blank?` / `#to_s` read opaque on every Rails app. Measured by toggling this file alone: the
|
|
1193
|
+
# named-pair census drained 137 sites on redmine and 56 on mastodon, and `rigor check` reported the
|
|
1194
|
+
# same diagnostics on both arms across eight survey targets. The plugin now answers a `Dynamic`
|
|
1195
|
+
# carrier, which this projection does not reach, so those off-chain calls read `Dynamic[top]` until
|
|
1196
|
+
# the carrier is projected here too (#2448).
|
|
1147
1197
|
#
|
|
1148
1198
|
# **Why the bottom and not next to `RbsDispatch`.** Sitting here, the tier can only ever replace a
|
|
1149
1199
|
# `Dynamic[top]` the typer was about to produce — the same ADR-5 argument {UniversalObjectDispatch}
|
|
@@ -1346,8 +1396,8 @@ module Rigor
|
|
|
1346
1396
|
# class is representable as the parent's singleton type — its singleton-method surface is
|
|
1347
1397
|
# the parent's (plus whatever the block defines, which we do not statically track here),
|
|
1348
1398
|
# so `Singleton[Parent]` lets downstream `klass.some_class_method` resolve. No parent →
|
|
1349
|
-
# `singleton(Object)
|
|
1350
|
-
# back to `Nominal[Class]` via the surrounding `meta_new` tail.
|
|
1399
|
+
# `singleton(Object)` (or the block's anonymous class, #319 below). Anything else (dynamic parent, more
|
|
1400
|
+
# than one positional, …) falls back to `Nominal[Class]` via the surrounding `meta_new` tail.
|
|
1351
1401
|
# #319 — a `Class.new do ... end` with no parent is NOT `Object`: the block body is a class body, so the
|
|
1352
1402
|
# class owns whatever that body defines. Answering `Singleton[Object]` handed the call site `Object`'s
|
|
1353
1403
|
# zero-arity `new` and `Object`'s method surface, so an `initialize(bucket)` written right there produced
|
|
@@ -76,6 +76,17 @@ module Rigor
|
|
|
76
76
|
(node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
# The `def` nodes a receiverless `public`, `private` or `protected` call takes as arguments (`private def x`,
|
|
80
|
+
# #2254). Ruby evaluates each one before the call, so it runs whenever the call's statement does. A named call
|
|
81
|
+
# with a symbol takes no def, and a call with a receiver is not read. A `module_function def` is not read here:
|
|
82
|
+
# its singleton copy is not modelled, so that gap stays as it was.
|
|
83
|
+
def visibility_call_defs(node)
|
|
84
|
+
return [] unless node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
85
|
+
return [] unless %i[public private protected].include?(node.name)
|
|
86
|
+
|
|
87
|
+
node.arguments&.arguments&.grep(Prism::DefNode) || []
|
|
88
|
+
end
|
|
89
|
+
|
|
79
90
|
# The mode after `stmt`, a direct statement of a body, for the readers that walk the statements: `mode`
|
|
80
91
|
# is `true`, `false` or `:uncertain` before it. A {.visibility_reset?} the statement runs ends the mode,
|
|
81
92
|
# and one it may not run (`:maybe_off`, below {.prescan_deferred}) makes an on mode uncertain. A
|
|
@@ -342,7 +353,6 @@ module Rigor
|
|
|
342
353
|
true
|
|
343
354
|
end
|
|
344
355
|
|
|
345
|
-
# The prescan's call arm.
|
|
346
356
|
def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
347
357
|
if bare?(node)
|
|
348
358
|
offsets << [node.location.start_offset, :on]
|
|
@@ -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`).
|
|
@@ -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`
|