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
|
@@ -10,7 +10,10 @@ require_relative "mutation_rejoin"
|
|
|
10
10
|
require_relative "deep_erasure"
|
|
11
11
|
require_relative "receiver_alias"
|
|
12
12
|
require_relative "refinement_mutation"
|
|
13
|
+
# `RemoverNarrowing` is loaded with the engine (`scope.rb`), not here: the engine-free value layer requires this file
|
|
14
|
+
# and must not pull in the remover narrowing and its holder scan (`spec/rigor/cli/effects_cache_probe_spec.rb`).
|
|
13
15
|
require_relative "rewrite_mutation"
|
|
16
|
+
require_relative "set_mutation"
|
|
14
17
|
require_relative "string_mutation"
|
|
15
18
|
|
|
16
19
|
module Rigor
|
|
@@ -19,8 +22,7 @@ module Rigor
|
|
|
19
22
|
# AND whose method is a known in-place mutator.
|
|
20
23
|
#
|
|
21
24
|
# Closes the **G1 / G2** flow-folding gaps documented at
|
|
22
|
-
# `docs/notes/20260521-mastodon-cluster4-flow-folding-triage.md
|
|
23
|
-
# [`docs/CURRENT_WORK.md`](../../../docs/CURRENT_WORK.md) § "Flow-folding". The user-visible
|
|
25
|
+
# `docs/notes/20260521-mastodon-cluster4-flow-folding-triage.md`. The user-visible
|
|
24
26
|
# symptom they shared was a spurious `flow.always-truthy-condition` on a `arr.size == N` /
|
|
25
27
|
# `arr.empty?` / `@arr.empty?` check that follows a loop body or sibling method that mutates
|
|
26
28
|
# `arr` / `@arr` in place.
|
|
@@ -76,6 +78,7 @@ module Rigor
|
|
|
76
78
|
# ARE also handled** (ivars live in the method-body scope, not the block-local scope).
|
|
77
79
|
#
|
|
78
80
|
# The remaining three items above are demand-gated; see ADR-56.
|
|
81
|
+
# rubocop:disable-next Metrics/ModuleLength
|
|
79
82
|
module MutationWidening
|
|
80
83
|
# Array mutators that change either the size or the element set of a literal-shape carrier
|
|
81
84
|
# (Tuple). Receiver-mutating methods only — non-mutating siblings (`map` vs `map!`,
|
|
@@ -107,7 +110,8 @@ module Rigor
|
|
|
107
110
|
# Every method name {#widen_for_mutator} responds to — the one set a body scan asks "could an
|
|
108
111
|
# in-place call on this name change its binding?" against (issue #587: the block-return
|
|
109
112
|
# threading gate). Derived from the tables rather than spelled out, so the scan and the
|
|
110
|
-
# widening it predicts cannot drift apart.
|
|
113
|
+
# widening it predicts cannot drift apart. The Set-only names ({SetMutation::MUTATORS}) stay out: the scans read
|
|
114
|
+
# a name without its receiver, and `merge` on a Hash returns a new Hash (issue #2294).
|
|
111
115
|
SHAPE_MUTATORS = (ARRAY_MUTATORS | HASH_MUTATORS | HashLookupMutation::MUTATORS | StringMutation::MUTATORS).freeze
|
|
112
116
|
|
|
113
117
|
# Methods that return the receiver (or a shallow copy) and cannot mutate it. They must not
|
|
@@ -134,10 +138,61 @@ module Rigor
|
|
|
134
138
|
# pre-join behaviour exactly.
|
|
135
139
|
NO_ARG_TYPES = [].freeze
|
|
136
140
|
|
|
137
|
-
|
|
141
|
+
# `rewritten_element` is the element a typed element rewriter stores ({RewriteMutation.typed}), or `nil`.
|
|
142
|
+
def widen_after_call(call_node:, current_scope:, arg_types: NO_ARG_TYPES, rewritten_element: nil)
|
|
138
143
|
return current_scope if pure_self_returner?(call_node.name)
|
|
139
144
|
|
|
140
|
-
|
|
145
|
+
if RemoverNarrowing.candidate?(call_node)
|
|
146
|
+
narrowed = narrow_remover_receiver(call_node, current_scope, arg_types)
|
|
147
|
+
return narrowed if narrowed
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
widen_receiver_aliases(call_node.receiver, call_node.name, current_scope, arg_types:, rewritten_element:,
|
|
151
|
+
removes_nil: removes_nil?(call_node))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Issue #2608 — a remover that narrows its receiver by what it removes ({RemoverNarrowing}): the receiver's
|
|
155
|
+
# binding widens as any mutator's does, then loses what the call provably took out. This answers before the
|
|
156
|
+
# #2555 remover floor, so a synthesised carrier a store grew keeps its precise reading (`parts << maybe_nil;
|
|
157
|
+
# parts.compact!` reads `Array[String]`). `nil` when the call narrows nothing; the caller then takes the
|
|
158
|
+
# ordinary path, the floor included.
|
|
159
|
+
def narrow_remover_receiver(call_node, scope, arg_types)
|
|
160
|
+
read = call_node.receiver
|
|
161
|
+
getter, builder = ALIAS_ACCESSORS[read.class]
|
|
162
|
+
name = ReceiverAlias.read_name(read)
|
|
163
|
+
current = scope.public_send(getter, name)
|
|
164
|
+
return nil if current.nil? || RemoverHolderScan.unsafe_receiver?(scope, name)
|
|
165
|
+
|
|
166
|
+
narrowed = RemoverNarrowing.narrow_shape(current, call_node)
|
|
167
|
+
if narrowed.nil?
|
|
168
|
+
synthesised = synthesised_binding?(scope, getter, name)
|
|
169
|
+
base = widen_for_mutator(current, call_node.name, arg_types:, synthesised:)
|
|
170
|
+
narrowed = RemoverNarrowing.narrow(base || current, call_node, scope)
|
|
171
|
+
end
|
|
172
|
+
return nil if narrowed.nil?
|
|
173
|
+
return scope if narrowed == current
|
|
174
|
+
|
|
175
|
+
MutationRejoin.rebind(scope, builder, name, narrowed, pre_state: current, kind: getter)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Issue #2555 — the removers that may take a `nil` out, which is every one but a `delete` / `delete?` given a
|
|
179
|
+
# non-nil literal: false positives outrank, so a positional remover (`pop`, `shift`, `slice!`), `subtract`
|
|
180
|
+
# (whose argument may hold `nil`) and a `delete` of an unknown value count. `Hash#delete` removes by key, so
|
|
181
|
+
# whatever value the key held may go ({.floor_grown_carrier} reads the receiver for it).
|
|
182
|
+
NIL_REMOVERS = %i[compact! reject! select! filter! delete_if keep_if clear pop shift slice!
|
|
183
|
+
subtract].to_set.freeze
|
|
184
|
+
NON_NIL_LITERALS = [
|
|
185
|
+
Prism::IntegerNode, Prism::FloatNode, Prism::RationalNode, Prism::ImaginaryNode, Prism::StringNode,
|
|
186
|
+
Prism::SymbolNode, Prism::TrueNode, Prism::FalseNode
|
|
187
|
+
].freeze
|
|
188
|
+
private_constant :NON_NIL_LITERALS
|
|
189
|
+
|
|
190
|
+
def removes_nil?(call_node)
|
|
191
|
+
return true if NIL_REMOVERS.include?(call_node.name)
|
|
192
|
+
return false unless %i[delete delete?].include?(call_node.name)
|
|
193
|
+
|
|
194
|
+
argument = call_node.arguments&.arguments&.first
|
|
195
|
+
NON_NIL_LITERALS.none? { |klass| argument.is_a?(klass) }
|
|
141
196
|
end
|
|
142
197
|
|
|
143
198
|
# True when `receiver` names at least one variable whose CURRENT binding is a literal-shape
|
|
@@ -148,10 +203,19 @@ module Rigor
|
|
|
148
203
|
|
|
149
204
|
ReceiverAlias.mutated_reads(receiver).any? do |read|
|
|
150
205
|
getter, = ALIAS_ACCESSORS[read.class]
|
|
151
|
-
|
|
206
|
+
next false if getter.nil?
|
|
207
|
+
|
|
208
|
+
name = ReceiverAlias.read_name(read)
|
|
209
|
+
shape_carrier?(scope.public_send(getter, name), synthesised: synthesised_binding?(scope, getter, name))
|
|
152
210
|
end
|
|
153
211
|
end
|
|
154
212
|
|
|
213
|
+
# Issue #2555 L3 — whether the binding `getter` / `name` reads carries the `:synthesised` construction fact
|
|
214
|
+
# ({Scope#construction_fact?}). Only a local can: an instance, class or global variable never carries one.
|
|
215
|
+
def synthesised_binding?(scope, getter, name)
|
|
216
|
+
getter == :local && scope.construction_fact?(name)
|
|
217
|
+
end
|
|
218
|
+
|
|
155
219
|
# A union carrying a shape member is joinable too (issue #645): {#widen_union} widens that
|
|
156
220
|
# member through the same join, so the arguments the caller would skip are the evidence it
|
|
157
221
|
# needs.
|
|
@@ -160,26 +224,32 @@ module Rigor
|
|
|
160
224
|
# An empty-witness `Difference` counts too (issue #936): the `non-empty-array[T]` arm joins the
|
|
161
225
|
# mutator's added element now, so its arguments are evidence this seam consumes rather than
|
|
162
226
|
# arguments nothing will read.
|
|
163
|
-
|
|
227
|
+
# A precise `Hash` counts too when its binding carries the `:synthesised` construction fact (issue #2555): the
|
|
228
|
+
# re-join grows it from these arguments.
|
|
229
|
+
def shape_carrier?(type, synthesised: false)
|
|
164
230
|
case type
|
|
165
231
|
when Type::Tuple, Type::HashShape then true
|
|
166
|
-
when Type::Constant then StringMutation.constant?(type)
|
|
232
|
+
when Type::Constant then StringMutation.constant?(type) || SetMutation.constant?(type)
|
|
167
233
|
# A re-openable nominal counts since issue #580: skipping it made the one-way door
|
|
168
234
|
# self-fulfilling, since the arm never saw the arg types it grows from.
|
|
169
|
-
|
|
235
|
+
# A precise `Array[E]` / `Set[E]` counts since issue #2361, which re-grows it from these arguments.
|
|
236
|
+
when Type::Nominal
|
|
237
|
+
MutationRejoin.regrowable_carrier?(type) || MutationRejoin.precise_array?(type) ||
|
|
238
|
+
SetMutation.regrowable?(type) || (synthesised && MutationRejoin.synthesised_carrier?(type))
|
|
170
239
|
when Type::Difference then type.removes_empty_witness?
|
|
171
|
-
when Type::Union then type.members.any? { |m| shape_carrier?(m) }
|
|
240
|
+
when Type::Union then type.members.any? { |m| shape_carrier?(m, synthesised:) }
|
|
172
241
|
else false
|
|
173
242
|
end
|
|
174
243
|
end
|
|
175
244
|
|
|
176
245
|
# Widens every variable `receiver` can evaluate to ({ReceiverAlias.mutated_reads}), against `method_name`'s
|
|
177
246
|
# mutator table.
|
|
178
|
-
def widen_receiver_aliases(receiver, method_name, current_scope, arg_types: NO_ARG_TYPES
|
|
247
|
+
def widen_receiver_aliases(receiver, method_name, current_scope, arg_types: NO_ARG_TYPES, rewritten_element: nil,
|
|
248
|
+
removes_nil: false)
|
|
179
249
|
return current_scope if receiver.nil?
|
|
180
250
|
|
|
181
251
|
ReceiverAlias.mutated_reads(receiver).reduce(current_scope) do |acc, read|
|
|
182
|
-
widen_alias_read(method_name, read, acc, arg_types:
|
|
252
|
+
widen_alias_read(method_name, read, acc, arg_types:, rewritten_element:, removes_nil:)
|
|
183
253
|
end
|
|
184
254
|
end
|
|
185
255
|
|
|
@@ -196,9 +266,8 @@ module Rigor
|
|
|
196
266
|
# always safe — it can only LOSE precision — so blindly propagating is sound regardless of
|
|
197
267
|
# whether the block actually runs.
|
|
198
268
|
#
|
|
199
|
-
# Recurses into nested
|
|
200
|
-
# g(y)`, `arr.push(x) if cond`) are all caught.
|
|
201
|
-
# `Prism::BlockNode`s — each block is processed by its own `eval_call`.
|
|
269
|
+
# Recurses into every nested node, blocks included, so chained / nested forms (`arr << f(x); arr <<
|
|
270
|
+
# g(y)`, `arr.push(x) if cond`) are all caught.
|
|
202
271
|
def widen_after_block(call_node:, outer_scope:)
|
|
203
272
|
block = call_node.block
|
|
204
273
|
return DeepErasure.widen_symbol_proc(call_node, outer_scope) if DeepErasure.symbol_proc_mutation?(block)
|
|
@@ -235,6 +304,9 @@ module Rigor
|
|
|
235
304
|
receiver = call_node.receiver
|
|
236
305
|
return scope if receiver.nil?
|
|
237
306
|
|
|
307
|
+
widened = widen_outer_element_read(call_node, scope)
|
|
308
|
+
return widened if widened
|
|
309
|
+
|
|
238
310
|
ReceiverAlias.candidates(receiver).reduce(scope) do |acc, read|
|
|
239
311
|
# A block-local read ({ReceiverAlias.block_local?}: a `depth == 0` local, or `it`) is not a
|
|
240
312
|
# capture of the outer scope, so widening its name against the OUTER scope would hit an
|
|
@@ -245,10 +317,27 @@ module Rigor
|
|
|
245
317
|
# after it re-adds the appended values' types onto the widened seed, so the seed's own value
|
|
246
318
|
# pinning is still justified (`out = [0]; arr.each { out << x }` never rewrites slot 0). The
|
|
247
319
|
# straight-line paths have no such join, which is why their default is `:widen` (issue #560).
|
|
248
|
-
widen_alias_read(call_node.name, read, acc, values: :keep)
|
|
320
|
+
widen_alias_read(call_node.name, read, acc, values: :keep, unread_arguments: !call_node.arguments.nil?)
|
|
249
321
|
end
|
|
250
322
|
end
|
|
251
323
|
|
|
324
|
+
# Issue #2287 — a mutator on an element read rooted at a captured local (`[0].each { |i| a[i] << 2 }`,
|
|
325
|
+
# `loop { a[0] << 2; break }`) mutates an element the outer binding still carries, and names no variable
|
|
326
|
+
# {ReceiverAlias.candidates} could widen. The element at that path widens in the outer scope as the
|
|
327
|
+
# straight-line {ElementReadWidening.widen_element_read} widens it; a block-local root is skipped as a
|
|
328
|
+
# block-local read is. Answers the widened scope, or `nil` when the receiver is no such read or nothing widened.
|
|
329
|
+
def widen_outer_element_read(call_node, scope)
|
|
330
|
+
path = ElementReadWidening.element_read_path(call_node.receiver)
|
|
331
|
+
return nil if path.nil?
|
|
332
|
+
|
|
333
|
+
read, steps = path
|
|
334
|
+
return nil if ReceiverAlias.block_local?(read)
|
|
335
|
+
|
|
336
|
+
current = scope.local(read.name)
|
|
337
|
+
widened = current && ElementReadWidening.widen_through_path(current, steps, call_node.name, NO_ARG_TYPES)
|
|
338
|
+
widened && scope.with_mutated_local(read.name, widened)
|
|
339
|
+
end
|
|
340
|
+
|
|
252
341
|
# The arms per kind of binding differ only in which pair of `Scope` accessors they use, so they are read out
|
|
253
342
|
# of one table rather than written once per kind: a carrier the widening responds to must not be widened on
|
|
254
343
|
# one kind of binding and left on another. `$g << x; $g == "k"` folded always-truthy on a `$g` Ruby holds
|
|
@@ -262,16 +351,37 @@ module Rigor
|
|
|
262
351
|
Prism::GlobalVariableReadNode => %i[global with_global]
|
|
263
352
|
})
|
|
264
353
|
|
|
265
|
-
|
|
354
|
+
# `unread_arguments` says the call passes arguments this seam did not type (the block-capture path): a Set,
|
|
355
|
+
# which has no block content join, takes one unknown store for it ({SetMutation.widen}).
|
|
356
|
+
def widen_alias_read(method_name, read, scope, values: :widen, arg_types: NO_ARG_TYPES, unread_arguments: false, # rubocop:disable Metrics/ParameterLists
|
|
357
|
+
rewritten_element: nil, removes_nil: false)
|
|
266
358
|
getter, builder = ALIAS_ACCESSORS[read.class]
|
|
267
359
|
return scope if getter.nil?
|
|
268
360
|
|
|
269
361
|
name = ReceiverAlias.read_name(read)
|
|
270
362
|
current = scope.public_send(getter, name)
|
|
271
|
-
|
|
272
|
-
|
|
363
|
+
synthesised = synthesised_binding?(scope, getter, name)
|
|
364
|
+
if synthesised && MutationRejoin.floors_grown_carrier?(scope, name, method_name)
|
|
365
|
+
return floor_grown_carrier(scope, builder, name, current, method_name, getter, removes_nil)
|
|
366
|
+
end
|
|
273
367
|
|
|
274
|
-
|
|
368
|
+
widened = widen_for_mutator(current, method_name, values:, arg_types:, unread_arguments:, rewritten_element:,
|
|
369
|
+
synthesised:)
|
|
370
|
+
scope = MutationRejoin.rebind(scope, builder, name, widened, pre_state: current, kind: getter) if widened
|
|
371
|
+
return scope unless synthesised && MutationRejoin.content_adder?(method_name)
|
|
372
|
+
|
|
373
|
+
MutationRejoin.grew_unfloored?(current, widened) ? scope.with_construction_fact(name, :grown) : scope
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Issue #2555 — a remover or filter on a synthesised carrier a store has grown. Nothing narrows the carrier back,
|
|
377
|
+
# so the stored arm would outlive the values the call removes (`parts << nil; parts.compact!`); the carrier takes
|
|
378
|
+
# the `Dynamic[top]` floor it would have carried without the fact. The binding keeps its facts: the floored
|
|
379
|
+
# carrier is gradual, so the synthesised re-join no longer applies to it, and a later remover floors it again.
|
|
380
|
+
def floor_grown_carrier(scope, builder, name, current, method_name, getter, removes_nil)
|
|
381
|
+
drop_nil = removes_nil || (method_name == :delete && MutationRejoin.hash_carrier?(current))
|
|
382
|
+
floored = MutationRejoin.floor_parameters(current, drop_nil:)
|
|
383
|
+
floored = widen_for_mutator(floored, method_name) || floored
|
|
384
|
+
MutationRejoin.rebind(scope, builder, name, floored, pre_state: current, kind: getter)
|
|
275
385
|
end
|
|
276
386
|
|
|
277
387
|
# Mutators that can land a NEW value in an EXISTING slot, falsifying that slot's value pinning
|
|
@@ -285,15 +395,32 @@ module Rigor
|
|
|
285
395
|
# Returns the widened type for a binding whose receiver is about to be mutated by
|
|
286
396
|
# `method_name`, or `nil` when no widening applies (binding is not a carrier the mutation
|
|
287
397
|
# stands for, OR the method is not a mutator for that shape, OR the carrier is a PRECISE
|
|
288
|
-
# nominal
|
|
289
|
-
|
|
398
|
+
# nominal the call stores nothing into — see {MutationRejoin.widen_precise_array}).
|
|
399
|
+
# A `rewritten_element` answers first for the carriers {RewriteMutation.typed} types (issue #2427).
|
|
400
|
+
# `synthesised` says the binding carries the `:synthesised` construction fact (issue #2555,
|
|
401
|
+
# {Scope#construction_fact?}): a precise nominal or Set it holds re-grows without the `Dynamic[top]` floor
|
|
402
|
+
# ({MutationRejoin.widen_synthesised}, {SetMutation.widen}).
|
|
403
|
+
def widen_for_mutator(type, method_name, values: :widen, arg_types: NO_ARG_TYPES, unread_arguments: false,
|
|
404
|
+
rewritten_element: nil, synthesised: false)
|
|
405
|
+
typed = rewritten_element && type && RewriteMutation.typed(type, method_name, rewritten_element)
|
|
406
|
+
return typed == type ? nil : typed if typed
|
|
407
|
+
|
|
408
|
+
widen_carrier(type, method_name, values:, arg_types:, unread_arguments:, rewritten_element:, synthesised:)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# {#widen_for_mutator} for a carrier no typed element rewrite answered.
|
|
412
|
+
def widen_carrier(type, method_name, values:, arg_types:, unread_arguments:, rewritten_element:,
|
|
413
|
+
synthesised: false)
|
|
290
414
|
values = :keep unless VALUE_REWRITING_MUTATORS.include?(method_name)
|
|
291
415
|
values = RewriteMutation.values_mode(type, method_name, values)
|
|
292
416
|
|
|
293
417
|
return nil if type.nil?
|
|
418
|
+
if SetMutation.set?(type)
|
|
419
|
+
return SetMutation.widen(type, method_name, arg_types, unread_arguments:, synthesised:)
|
|
420
|
+
end
|
|
294
421
|
|
|
295
422
|
case type
|
|
296
|
-
when Type::Nominal then MutationRejoin.widen_nominal(type, method_name, values:, arg_types:)
|
|
423
|
+
when Type::Nominal then MutationRejoin.widen_nominal(type, method_name, values:, arg_types:, synthesised:)
|
|
297
424
|
when Type::Tuple
|
|
298
425
|
return nil unless ARRAY_MUTATORS.include?(method_name)
|
|
299
426
|
|
|
@@ -307,7 +434,7 @@ module Rigor
|
|
|
307
434
|
when Type::Difference
|
|
308
435
|
widen_difference(type, method_name, arg_types: arg_types)
|
|
309
436
|
when Type::Union
|
|
310
|
-
widen_union(type, method_name, values
|
|
437
|
+
widen_union(type, method_name, values:, arg_types:, unread_arguments:, rewritten_element:, synthesised:)
|
|
311
438
|
end
|
|
312
439
|
end
|
|
313
440
|
|
|
@@ -325,12 +452,14 @@ module Rigor
|
|
|
325
452
|
#
|
|
326
453
|
# `nil` when no member widened — a union with no carrier member, or one whose carriers are
|
|
327
454
|
# already nominal, is untouched exactly as the scalar path leaves them.
|
|
328
|
-
def widen_union(union, method_name, values: :widen, arg_types: NO_ARG_TYPES
|
|
455
|
+
def widen_union(union, method_name, values: :widen, arg_types: NO_ARG_TYPES, unread_arguments: false,
|
|
456
|
+
rewritten_element: nil, synthesised: false)
|
|
329
457
|
widened_any = false
|
|
330
458
|
members = union.members.map do |member|
|
|
331
459
|
next member unless mutation_carrier?(member, method_name)
|
|
332
460
|
|
|
333
|
-
widened = widen_for_mutator(member, method_name, values
|
|
461
|
+
widened = widen_for_mutator(member, method_name, values:, arg_types:, unread_arguments:, rewritten_element:,
|
|
462
|
+
synthesised:)
|
|
334
463
|
next member if widened.nil?
|
|
335
464
|
|
|
336
465
|
widened_any = true
|
|
@@ -348,7 +477,8 @@ module Rigor
|
|
|
348
477
|
(ARRAY_MUTATORS.include?(method_name) && ContentJoin.array_residue(member).empty?) ||
|
|
349
478
|
((HASH_MUTATORS.include?(method_name) || HashLookupMutation::MUTATORS.include?(method_name)) &&
|
|
350
479
|
ContentJoin.hash_residue(member).empty?) ||
|
|
351
|
-
(StringMutation::MUTATORS.include?(method_name) && StringMutation.constant?(member))
|
|
480
|
+
(StringMutation::MUTATORS.include?(method_name) && StringMutation.constant?(member)) ||
|
|
481
|
+
(SetMutation::MUTATORS.include?(method_name) && SetMutation.carrier?(member))
|
|
352
482
|
end
|
|
353
483
|
|
|
354
484
|
# Joins the element evidence the mutator's own ARGUMENTS introduce into the already-widened
|
|
@@ -608,6 +608,26 @@ module Rigor
|
|
|
608
608
|
truthy
|
|
609
609
|
end
|
|
610
610
|
|
|
611
|
+
# Issue #2443 — `scope` with what every match binds, laid over the bindings it holds, for the block of a call
|
|
612
|
+
# that yields only after it has matched in the caller's frame ({MatchRebinding::YieldingMatch}): `$~` a
|
|
613
|
+
# `MatchData`, and `$&` and the pre- and post-match a `String`. The numbered groups and `$+` keep the bindings
|
|
614
|
+
# `scope` gives them. Typing them from the call's own pattern needs the group walk
|
|
615
|
+
# ({#unconditional_capture_groups}) to read every source right, which it does not yet (#2472), and forgetting
|
|
616
|
+
# them would report a group an earlier match in the method narrowed.
|
|
617
|
+
def yielded_match_scope(scope)
|
|
618
|
+
string_t = Type::Combinator.nominal_of("String")
|
|
619
|
+
scope.with_global(:$~, Type::Combinator.nominal_of("MatchData"))
|
|
620
|
+
.with_global(:$&, string_t).with_global(:$`, string_t).with_global(:$', string_t)
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# Issue #2443 — true when every member of `type` is a String (a project subclass included) or a `Dynamic` that
|
|
624
|
+
# is untyped or may hold only Strings, Symbols and nil ({#match_member_kind}). A `nil` member never runs the
|
|
625
|
+
# block, so it counts neither way.
|
|
626
|
+
def string_match_receiver?(type, scope)
|
|
627
|
+
kinds = match_member_kinds(type, scope) - [:nil]
|
|
628
|
+
!kinds.empty? && kinds.all? { |kind| %i[string string_subclass dynamic].include?(kind) }
|
|
629
|
+
end
|
|
630
|
+
|
|
611
631
|
# Internal analyser. Returns `[truthy_scope, falsey_scope]` when the predicate shape is
|
|
612
632
|
# recognised, or `nil` to signal "no narrowing" so the public surface can fall back to the
|
|
613
633
|
# entry scope. Issue #1359 — a condition whose value is a `gets`-family reader's, the bare call
|
|
@@ -968,12 +988,22 @@ module Rigor
|
|
|
968
988
|
end
|
|
969
989
|
|
|
970
990
|
def analyse_local_read(node, scope)
|
|
971
|
-
|
|
991
|
+
local_truthiness_edges(node.name, scope)
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
# The falsey edge is where an optimistic lookup's miss lands (ADR-101, issue #2306): `Scope#with_local`
|
|
995
|
+
# drops the issue #286 mark as a write does, so the edge re-takes the one `scope` holds. Without it the
|
|
996
|
+
# join after `if v; …; end` with no `else` lost the mark on both arms and read `v` as nil-free, although
|
|
997
|
+
# a miss reaches that join through the implicit else. The truthy edge proves the value non-nil, so it
|
|
998
|
+
# keeps dropping the mark.
|
|
999
|
+
def local_truthiness_edges(name, scope)
|
|
1000
|
+
current = scope.local(name)
|
|
972
1001
|
return nil if current.nil?
|
|
973
1002
|
|
|
1003
|
+
falsey = scope.with_local(name, narrow_falsey(current))
|
|
974
1004
|
[
|
|
975
|
-
scope.with_local(
|
|
976
|
-
|
|
1005
|
+
scope.with_local(name, narrow_truthy(current)),
|
|
1006
|
+
OptimisticOrigin.carry_local_mark(scope, falsey, name, nil)
|
|
977
1007
|
]
|
|
978
1008
|
end
|
|
979
1009
|
|
|
@@ -987,13 +1017,7 @@ module Rigor
|
|
|
987
1017
|
# holds the just-bound value; the narrowing contract on the surrounding `if` is the
|
|
988
1018
|
# same.
|
|
989
1019
|
def analyse_local_write(node, scope)
|
|
990
|
-
|
|
991
|
-
return nil if current.nil?
|
|
992
|
-
|
|
993
|
-
[
|
|
994
|
-
scope.with_local(node.name, narrow_truthy(current)),
|
|
995
|
-
scope.with_local(node.name, narrow_falsey(current))
|
|
996
|
-
]
|
|
1020
|
+
local_truthiness_edges(node.name, scope)
|
|
997
1021
|
end
|
|
998
1022
|
|
|
999
1023
|
# Truthy guard on an instance variable. Covers both the bare read — `if @ivar`, the
|
|
@@ -1007,9 +1031,11 @@ module Rigor
|
|
|
1007
1031
|
current = scope.ivar(node.name)
|
|
1008
1032
|
return nil if current.nil?
|
|
1009
1033
|
|
|
1034
|
+
# The falsey edge keeps the optimistic mark, as {#local_truthiness_edges} explains.
|
|
1035
|
+
falsey = scope.with_ivar(node.name, narrow_falsey(current))
|
|
1010
1036
|
[
|
|
1011
1037
|
scope.with_ivar(node.name, narrow_truthy(current)),
|
|
1012
|
-
|
|
1038
|
+
OptimisticOrigin.carry_ivar_mark(scope, falsey, node.name, nil)
|
|
1013
1039
|
]
|
|
1014
1040
|
end
|
|
1015
1041
|
|
|
@@ -1180,15 +1206,6 @@ module Rigor
|
|
|
1180
1206
|
refinements
|
|
1181
1207
|
end
|
|
1182
1208
|
|
|
1183
|
-
# Recognised CallNode predicates:
|
|
1184
|
-
# - `recv.nil?` (Slice 6 phase 1, no args, no block)
|
|
1185
|
-
# - unary `!recv` (`name == :!`, no args, no block)
|
|
1186
|
-
# - `recv.is_a?(C)` / `recv.kind_of?(C)` / `recv.instance_of?(C)` with a single
|
|
1187
|
-
# static-constant argument and no block (Slice 6 phase 2 sub-phase 1).
|
|
1188
|
-
# - `local == literal` / `literal == local` and the `!=` mirror for trusted static
|
|
1189
|
-
# literals (Slice 6 phase 2 sub-phase 2).
|
|
1190
|
-
# Anything else returns nil so the surrounding analyser falls through to the
|
|
1191
|
-
# no-narrowing fallback.
|
|
1192
1209
|
# Issue #1223 — `(x = e).nil?`, `(x ||= e).empty?`, `(@x = e).is_a?(C)`. A write's value is the binding
|
|
1193
1210
|
# it leaves, and a predicate is narrowed in the scope after its receiver ran, so the call narrows the
|
|
1194
1211
|
# variable exactly as the same call on a read of it would. Every analyser keys its receiver on a read
|
|
@@ -2466,10 +2483,10 @@ module Rigor
|
|
|
2466
2483
|
narrowed == original ? :relational : :local_binding
|
|
2467
2484
|
end
|
|
2468
2485
|
|
|
2469
|
-
# `recv.is_a?(C)` / `recv.kind_of?(C)` / `recv.instance_of?(C)` narrowing. The
|
|
2470
|
-
# MUST be a `LocalVariableReadNode` (so we have a name to rebind), and the argument
|
|
2486
|
+
# `recv.is_a?(C)` / `recv.kind_of?(C)` / `recv.instance_of?(C)` narrowing. The argument
|
|
2471
2487
|
# MUST be a single static constant reference (`Foo` or `Foo::Bar`) we can resolve to a
|
|
2472
|
-
# qualified class name.
|
|
2488
|
+
# qualified class name. The receiver is a local, a global, constant or instance variable
|
|
2489
|
+
# ({#receiver_slot}) or a single-hop chain; anything else falls through to "no narrowing".
|
|
2473
2490
|
def analyse_class_predicate(node, scope, exact:)
|
|
2474
2491
|
return nil if node.arguments.nil?
|
|
2475
2492
|
return nil unless node.arguments.arguments.size == 1
|
|
@@ -2538,14 +2555,13 @@ module Rigor
|
|
|
2538
2555
|
class_predicate_scopes(scope, node.receiver.name, current, class_name, exact: exact)
|
|
2539
2556
|
end
|
|
2540
2557
|
|
|
2541
|
-
# Stable single-hop method-chain narrowing
|
|
2542
|
-
# receiver narrowing across stable receivers"). When the predicate's receiver is
|
|
2558
|
+
# Stable single-hop method-chain narrowing. When the predicate's receiver is
|
|
2543
2559
|
# `<local/ivar>.<method>` with no args and no block, record the truthy / falsey
|
|
2544
2560
|
# narrowing in `Scope#method_chain_narrowings` keyed on the chain address. The
|
|
2545
2561
|
# dominated body's identical chain reads then observe the narrowed type through
|
|
2546
2562
|
# `ExpressionTyper#call_type_for`'s lookup.
|
|
2547
2563
|
#
|
|
2548
|
-
# Heuristic-by-design
|
|
2564
|
+
# Heuristic-by-design: a second call to `x.last` could in
|
|
2549
2565
|
# principle return a different value than the first. The chain is dropped on (1)
|
|
2550
2566
|
# receiver variable rebind (handled inside `Scope#with_local` / `#with_ivar`), and (2)
|
|
2551
2567
|
# any intervening call against the same root receiver (handled by
|
|
@@ -2864,9 +2880,7 @@ module Rigor
|
|
|
2864
2880
|
# - For each fact, the corresponding positional argument (matched by parameter name in
|
|
2865
2881
|
# the selected overload) MUST be a `Prism::LocalVariableReadNode` for narrowing to
|
|
2866
2882
|
# apply.
|
|
2867
|
-
# - When the target is `self`, narrowing applies to the receiver
|
|
2868
|
-
# not yet narrow `self` itself, so `self`-targeted facts are accepted by the merger
|
|
2869
|
-
# but currently produce no scope edits.
|
|
2883
|
+
# - When the target is `self`, narrowing applies to the receiver ({#apply_self_fact}).
|
|
2870
2884
|
def analyse_rbs_extended_contribution(node, scope)
|
|
2871
2885
|
method_def = resolve_rbs_extended_method(node, scope)
|
|
2872
2886
|
return nil if method_def.nil?
|
|
@@ -3403,8 +3417,8 @@ module Rigor
|
|
|
3403
3417
|
# extend record that is structurally incomplete: `Widget.extend(m)` at a call site and an `extend`
|
|
3404
3418
|
# written in a file outside the analysed set are both invisible to the walk behind
|
|
3405
3419
|
# {Scope#singleton_extends_of}, and the declaration-side record joined to it below is no more
|
|
3406
|
-
# complete — RBS says nothing about a class it does not declare. A shape that turned this table
|
|
3407
|
-
# `:subclass` verdict would have to be right about ABSENCE too, and it cannot be. Preserving
|
|
3420
|
+
# complete — RBS says nothing about a class it does not declare. A shape that turned this table
|
|
3421
|
+
# into a positive `:subclass` verdict would have to be right about ABSENCE too, and it cannot be. Preserving
|
|
3408
3422
|
# `Singleton[C]` would also hand the arm a receiver whose singleton method table has no `clamp` on
|
|
3409
3423
|
# it, trading the unreachable-clause false positive for an undefined-method one — the same trap
|
|
3410
3424
|
# #657 recorded on the `Constant` carrier.
|
|
@@ -3586,7 +3600,7 @@ module Rigor
|
|
|
3586
3600
|
rbs_type.is_a?(RBS::Types::Literal) && rbs_type.literal == value
|
|
3587
3601
|
end
|
|
3588
3602
|
|
|
3589
|
-
# The
|
|
3603
|
+
# The three receiver shapes with a scope binding the edges can rebind, mirroring
|
|
3590
3604
|
# `apply_self_fact`. A method chain or arbitrary expression has none, so it reads nil and
|
|
3591
3605
|
# the caller declines.
|
|
3592
3606
|
def receiver_binding_type(receiver, scope)
|
|
@@ -3696,7 +3710,7 @@ module Rigor
|
|
|
3696
3710
|
|
|
3697
3711
|
# Walks a call chain down to its `&.` link, collecting the suffix method names applied
|
|
3698
3712
|
# above it (outermost first). Declines unless the chain bottoms out in a `&.` call on a
|
|
3699
|
-
# direct local
|
|
3713
|
+
# direct local, instance-variable, global or constant read — the slots a scope can rewrite — and unless
|
|
3700
3714
|
# every suffix is a bare reader or predicate, since an argument or a block could make the
|
|
3701
3715
|
# answer depend on something other than the receiver.
|
|
3702
3716
|
#
|
|
@@ -18,7 +18,7 @@ module Rigor
|
|
|
18
18
|
# that happens to have been produced optimistically.
|
|
19
19
|
#
|
|
20
20
|
# Issue #286: the `if` / `unless` branch elision is one consumer of `Narrowing.predicate_certainty`; the
|
|
21
|
-
# other two are `flow.always-truthy-condition` and
|
|
21
|
+
# other two are `flow.always-truthy-condition` and `StatementEvaluator#right_operand_dead?`, and the
|
|
22
22
|
# spec passage above binds all three. This channel is what lets a certainty judgment tell the two apart.
|
|
23
23
|
#
|
|
24
24
|
# Issue #313: the mark is attached to a *value* — the call node that produced it, or the local / ivar it
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require_relative "mutation_widening"
|
|
5
|
+
require_relative "receiver_alias"
|
|
6
|
+
require_relative "../type"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
# Issue #2499 rule 3 (part of #2494) — the stdlib calls that fill a String the caller passes in. After
|
|
11
|
+
# `io.read(n, buf)`, `io.readpartial(n, buf)`, `io.sysread(n, buf)` and `io.read_nonblock(n, buf)` the buffer
|
|
12
|
+
# holds what was read, and after `StringIO.new(s)` every later write through the StringIO lands in `s`. The
|
|
13
|
+
# argument (a local, instance, class or global variable read) is widened as an in-place `replace` of it would
|
|
14
|
+
# widen it, so a `buf = +""` seed stops reading `""` after the call.
|
|
15
|
+
#
|
|
16
|
+
# A built-in catalogue keyed by method name and argument position, not a receiver-typed dispatch: a method of
|
|
17
|
+
# another class with the same name and a second positional argument loses only precision on that argument.
|
|
18
|
+
module OutBufferMutation
|
|
19
|
+
# Method name => the zero-based position of the buffer argument.
|
|
20
|
+
READERS = { read: 1, readpartial: 1, sysread: 1, read_nonblock: 1 }.freeze
|
|
21
|
+
private_constant :READERS
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# `scope` with the buffer argument of `call_node` widened, or `scope` itself when the call is no such call or the
|
|
26
|
+
# argument holds no String: `Rails.cache.read(key, opts)` passes an options Hash that keeps its shape.
|
|
27
|
+
def floor(call_node, scope)
|
|
28
|
+
buffer = buffer_argument(call_node)
|
|
29
|
+
return scope if buffer.nil?
|
|
30
|
+
|
|
31
|
+
getter, = MutationWidening::ALIAS_ACCESSORS[buffer.class]
|
|
32
|
+
return scope unless string_carrier?(scope.public_send(getter, ReceiverAlias.read_name(buffer)))
|
|
33
|
+
|
|
34
|
+
MutationWidening.widen_alias_read(:replace, buffer, scope)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# A `Constant<String>` or the `String` nominal, or a union of nothing else.
|
|
38
|
+
def string_carrier?(type)
|
|
39
|
+
case type
|
|
40
|
+
when Type::Constant then type.value.is_a?(String)
|
|
41
|
+
when Type::Nominal then type.class_name == "String"
|
|
42
|
+
when Type::Union then type.members.all? { |member| string_carrier?(member) }
|
|
43
|
+
else false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def buffer_argument(call_node)
|
|
48
|
+
arguments = call_node.arguments&.arguments
|
|
49
|
+
return nil if arguments.nil?
|
|
50
|
+
|
|
51
|
+
position = READERS[call_node.name]
|
|
52
|
+
position = 0 if position.nil? && string_io_new?(call_node)
|
|
53
|
+
return nil if position.nil?
|
|
54
|
+
|
|
55
|
+
argument = arguments[position]
|
|
56
|
+
MutationWidening::ALIAS_ACCESSORS.key?(argument.class) ? argument : nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def string_io_new?(call_node)
|
|
60
|
+
return false unless call_node.name == :new
|
|
61
|
+
|
|
62
|
+
receiver = call_node.receiver
|
|
63
|
+
case receiver
|
|
64
|
+
when Prism::ConstantReadNode then receiver.name == :StringIO
|
|
65
|
+
when Prism::ConstantPathNode then receiver.parent.nil? && receiver.name == :StringIO
|
|
66
|
+
else false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
private_class_method :buffer_argument, :string_io_new?, :string_carrier?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -95,11 +95,10 @@ module Rigor
|
|
|
95
95
|
# may type in a later round once its own argument resolves). Unresolved call sites do not
|
|
96
96
|
# contribute.
|
|
97
97
|
#
|
|
98
|
-
# What it does NOT do yet
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
# because the only consumer is the protection metric, which runs no diagnostics).
|
|
98
|
+
# What it does NOT do yet: typing keyword / optional / rest / block parameters themselves,
|
|
99
|
+
# inherited-method receivers, top-level helpers, and a rigorous closed-call-site-set proof
|
|
100
|
+
# (the union is optimistic over resolved sites; the `check` walk tolerates that because an
|
|
101
|
+
# inferred parameter is only a lower bound, which the in-body rules decline on, WD6b).
|
|
103
102
|
class ParameterInferenceCollector
|
|
104
103
|
EMPTY = {}.freeze
|
|
105
104
|
|
|
@@ -120,7 +119,7 @@ module Rigor
|
|
|
120
119
|
# @param files — project `.rb` paths to scan for call sites.
|
|
121
120
|
# @param target_ruby — Prism parse target.
|
|
122
121
|
# @param max_rounds — the WD5 fixpoint cap (1 = single-level).
|
|
123
|
-
# @return
|
|
122
|
+
# @return the inferred parameter table, frozen.
|
|
124
123
|
def self.collect(files:, environment:, target_ruby: nil, max_rounds: DEFAULT_ROUNDS, workers: 0)
|
|
125
124
|
new(files: files, environment: environment, target_ruby: target_ruby,
|
|
126
125
|
max_rounds: max_rounds, workers: workers).collect
|
|
@@ -13,7 +13,7 @@ module Rigor
|
|
|
13
13
|
#
|
|
14
14
|
# ADR-17 slice 2 gave `pre_eval:` a project-wide **method** registry ({ProjectPatchedMethods}). A
|
|
15
15
|
# constant declared in the same file stayed invisible across the file boundary: `Scope#in_source_constants`
|
|
16
|
-
#
|
|
16
|
+
# was per-file and kept out of `Runner#project_scope_seed_tables`, so only class-shaped
|
|
17
17
|
# constants (a `class` / `module` declaration, or one of the four meta-new forms `ScopeIndexer`'s
|
|
18
18
|
# `record_class_new_constant_decl` promotes) ever crossed. `TIMEOUT = 30` in a listed file read as
|
|
19
19
|
# `Dynamic[top]` everywhere else.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../type"
|
|
4
4
|
require_relative "external_ancestor_resolution"
|
|
5
|
+
require_relative "../run_scoped_slots"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Inference
|
|
@@ -19,7 +20,7 @@ module Rigor
|
|
|
19
20
|
NO_TARGETS = [].freeze
|
|
20
21
|
private_constant :NO_TARGETS
|
|
21
22
|
|
|
22
|
-
MEMO_KEY = :__rigor_project_method_ownership_memo__
|
|
23
|
+
MEMO_KEY = RunScopedSlots.register(:__rigor_project_method_ownership_memo__)
|
|
23
24
|
private_constant :MEMO_KEY
|
|
24
25
|
|
|
25
26
|
module_function
|
|
@@ -72,13 +73,15 @@ module Rigor
|
|
|
72
73
|
# `Scope` merges each file's discovery with the project pre-pass, so the slot turns over per file, and
|
|
73
74
|
# an ADR-46 dependency the first lookup recorded belongs to the file every later hit serves. Pool
|
|
74
75
|
# workers are separate processes, and a Ractor has its own `Thread.current`. The `:ancestry` bucket
|
|
75
|
-
# belongs to {ClosureEscapeAnalyzer}'s ancestry step.
|
|
76
|
+
# belongs to {ClosureEscapeAnalyzer}'s ancestry step. The `:block_runs` bucket belongs to
|
|
77
|
+
# {BlockRunCount}.
|
|
76
78
|
def memo(scope)
|
|
77
79
|
discovery = scope.discovery
|
|
78
80
|
environment = scope.environment
|
|
79
81
|
slot = Thread.current[MEMO_KEY]
|
|
80
82
|
unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
|
|
81
|
-
|
|
83
|
+
buckets = { instance: {}, singleton: {}, ancestry: {}, block_runs: {}.compare_by_identity }
|
|
84
|
+
slot = [discovery, environment, buckets]
|
|
82
85
|
Thread.current[MEMO_KEY] = slot
|
|
83
86
|
end
|
|
84
87
|
slot[2]
|