rigortype 0.4.5 → 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 +61 -5
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +192 -27
- data/docs/manual/03-configuration.md +38 -4
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +47 -3
- data/docs/manual/12-caching.md +9 -1
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +49 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +2 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- 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 +105 -12
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -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/rbs_receiver_surface.rb +66 -3
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- 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 +53 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +217 -52
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +15 -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 +154 -0
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +37 -0
- 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 +202 -43
- data/lib/rigor/analysis/path_expansion.rb +41 -0
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +7 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
- data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
- 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/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +233 -120
- 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 +38 -33
- 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 +60 -18
- 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 +188 -93
- 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 +27 -21
- 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 +17 -13
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +14 -8
- 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 +60 -4
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +22 -18
- data/lib/rigor/cli/plugins_renderer.rb +34 -8
- 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 +23 -16
- data/lib/rigor/cli/sig_gen_command.rb +130 -34
- 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 +18 -12
- data/lib/rigor/cli/type_of_project_scope.rb +24 -0
- 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 +17 -6
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +86 -34
- 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 +592 -70
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +136 -32
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +44 -11
- 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 +126 -0
- 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 +170 -0
- 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 +204 -12
- data/lib/rigor/inference/definer_resolution.rb +21 -5
- 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/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +173 -57
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +110 -1
- 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 +246 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +57 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +65 -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 +113 -12
- 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 +7 -3
- 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 +59 -24
- 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 +109 -29
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +23 -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 +162 -29
- data/lib/rigor/inference/narrowing.rb +113 -38
- data/lib/rigor/inference/object_mixins.rb +13 -1
- 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_seed_tables.rb +2 -1
- data/lib/rigor/inference/project_yielder.rb +122 -0
- 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 +9 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
- 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/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +614 -146
- data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
- 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 +3036 -278
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
- 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 +9 -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 +28 -9
- 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 +98 -4
- 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 +12 -6
- 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 +82 -3
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +26 -11
- 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 +38 -16
- data/lib/rigor/plugin/registry.rb +41 -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 +96 -2
- 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 +25 -2
- 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 +379 -36
- 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 +668 -84
- 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 +7 -6
- 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/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +21 -12
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- 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 +13 -16
- data/lib/rigor/type/constant.rb +48 -7
- data/lib/rigor/type/hash_shape.rb +26 -40
- data/lib/rigor/type/struct_instance.rb +3 -2
- 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 +89 -70
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -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 +79 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -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-devise/lib/rigor/plugin/devise.rb +5 -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 +7 -6
- 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 +102 -11
- 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 +5 -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/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +0 -2
- 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/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +10 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +6 -5
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +6 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/scope.rbs +51 -50
- data/sig/rigor/type.rbs +0 -6
- 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 +55 -3
|
@@ -10,8 +10,10 @@ require_relative "../source/constant_path"
|
|
|
10
10
|
require_relative "../source/node_children"
|
|
11
11
|
require_relative "../source/node_walker"
|
|
12
12
|
require_relative "../analysis/self_call_resolution_recorder"
|
|
13
|
+
require_relative "../run_scoped_slots"
|
|
13
14
|
require_relative "block_call_timing"
|
|
14
15
|
require_relative "block_repetition"
|
|
16
|
+
require_relative "project_yielder"
|
|
15
17
|
require_relative "block_parameter_binder"
|
|
16
18
|
require_relative "method_parameter_binder"
|
|
17
19
|
require_relative "body_fixpoint"
|
|
@@ -344,7 +346,7 @@ module Rigor
|
|
|
344
346
|
self.class.recorded_inject_accumulator(block, scope)
|
|
345
347
|
end
|
|
346
348
|
|
|
347
|
-
INJECT_ACCUMULATOR_KEY = :__rigor_inject_accumulator__
|
|
349
|
+
INJECT_ACCUMULATOR_KEY = RunScopedSlots.register(:__rigor_inject_accumulator__)
|
|
348
350
|
private_constant :INJECT_ACCUMULATOR_KEY
|
|
349
351
|
|
|
350
352
|
# The accumulator type {#record_inject_accumulator} stored for `block` when the call was typed from `scope`
|
|
@@ -359,13 +361,18 @@ module Rigor
|
|
|
359
361
|
# recording run to harvest each analyzed callee's observed call keys → return descriptors. Returns an
|
|
360
362
|
# empty hash when no bucket exists (a run that memoised nothing). Class method: the bucket lives on
|
|
361
363
|
# `Thread.current`, independent of any one `ExpressionTyper` instance.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
364
|
+
#
|
|
365
|
+
# `bucket` defaults to the calling thread's current one; `Runner#run` keeps the bucket its run filled
|
|
366
|
+
# ({.current_return_memo}) and passes it, since the run releases the thread's slot when it ends.
|
|
367
|
+
def self.harvest_return_memo(bucket = current_return_memo)
|
|
368
|
+
return {} if bucket.nil?
|
|
365
369
|
|
|
366
|
-
|
|
370
|
+
bucket.transform_values(&:values)
|
|
367
371
|
end
|
|
368
372
|
|
|
373
|
+
# The calling thread's return memo bucket (`{ def_node => { descriptor => MemoEntry } }`), or nil.
|
|
374
|
+
def self.current_return_memo = Thread.current[RETURN_MEMO_KEY]&.last
|
|
375
|
+
|
|
369
376
|
# Issue #720 — the value the block passed by the call site currently being re-typed evaluates to, or
|
|
370
377
|
# `nil` outside such a frame. Read by {#type_of_yield}.
|
|
371
378
|
#
|
|
@@ -1051,7 +1058,7 @@ module Rigor
|
|
|
1051
1058
|
# optimistic-carrier decline) and one terminating-arm rule, and cannot drift apart again. Only the
|
|
1052
1059
|
# value is read; the post-scope belongs to whichever walk owns the statement.
|
|
1053
1060
|
def type_of_conditional(node)
|
|
1054
|
-
scope.evaluate(node, tracer: tracer).first
|
|
1061
|
+
LoopValueRecord.fetch(node, scope) || scope.evaluate(node, tracer: tracer).first
|
|
1055
1062
|
end
|
|
1056
1063
|
|
|
1057
1064
|
def type_of_else(node)
|
|
@@ -1099,7 +1106,6 @@ module Rigor
|
|
|
1099
1106
|
conditions = when_node.respond_to?(:conditions) ? when_node.conditions : []
|
|
1100
1107
|
body_scope, next_scope = Narrowing.case_when_scopes(node.predicate, conditions, clause_scope)
|
|
1101
1108
|
certainty = case_when_branch_certainty(subject_type, when_node)
|
|
1102
|
-
# :no — drop the branch
|
|
1103
1109
|
candidates << case_arm_type(when_node, body_scope) unless certainty == :no
|
|
1104
1110
|
if certainty == :yes
|
|
1105
1111
|
reached_yes = true
|
|
@@ -1272,11 +1278,22 @@ module Rigor
|
|
|
1272
1278
|
Type::Combinator.bot
|
|
1273
1279
|
end
|
|
1274
1280
|
|
|
1275
|
-
# `while`
|
|
1276
|
-
#
|
|
1277
|
-
#
|
|
1278
|
-
|
|
1279
|
-
|
|
1281
|
+
# A `while` / `until` / `for` loop in value position. Like {#type_of_conditional}, it delegates to the
|
|
1282
|
+
# statement evaluator, which owns the loop's value: `nil` or a `while` / `until` loop's `break` values
|
|
1283
|
+
# (issue #2031), and a `for` loop's collection or its `break` values (issue #1216).
|
|
1284
|
+
#
|
|
1285
|
+
# A `while` / `until` loop no `break` leaves is `nil` without evaluating its body, as
|
|
1286
|
+
# {StatementEvaluator#loop_value} answers it: a loop that is a call's receiver or argument is typed again by
|
|
1287
|
+
# every narrowing and rule that types that operand, and evaluating it each time re-ran the passes of every loop
|
|
1288
|
+
# nested in it.
|
|
1289
|
+
def type_of_loop(node)
|
|
1290
|
+
return Type::Combinator.constant_of(nil) if break_free_condition_loop?(node)
|
|
1291
|
+
|
|
1292
|
+
LoopValueRecord.fetch(node, scope) || scope.evaluate(node, tracer: tracer).first
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
def break_free_condition_loop?(node)
|
|
1296
|
+
!node.is_a?(Prism::ForNode) && !block_level_jump?(node.statements, Prism::BreakNode)
|
|
1280
1297
|
end
|
|
1281
1298
|
|
|
1282
1299
|
def type_of_lambda(_node)
|
|
@@ -1507,9 +1524,8 @@ module Rigor
|
|
|
1507
1524
|
|
|
1508
1525
|
# Stable single-hop chain narrowing — `receiver.method` after an `is_a?` / `kind_of?` / `instance_of?`
|
|
1509
1526
|
# predicate established the narrowing on the dominated edge. The call MUST be no-arg + no-block +
|
|
1510
|
-
# rooted at a local-var / ivar read; everything else falls through to the standard dispatcher.
|
|
1511
|
-
#
|
|
1512
|
-
# single-hop scope.
|
|
1527
|
+
# rooted at a local-var / ivar read; everything else falls through to the standard dispatcher. The
|
|
1528
|
+
# single hop is Law-of-Demeter-justified.
|
|
1513
1529
|
def method_chain_narrowing_for(node)
|
|
1514
1530
|
return nil unless node.is_a?(Prism::CallNode)
|
|
1515
1531
|
return nil unless node.block.nil?
|
|
@@ -1759,7 +1775,23 @@ module Rigor
|
|
|
1759
1775
|
def call_type_for(node)
|
|
1760
1776
|
return safe_navigation_call_type(node) if node.safe_navigation?
|
|
1761
1777
|
|
|
1762
|
-
attribute_write_value(node, call_dispatch_type_for(node))
|
|
1778
|
+
memo_object_value(node, attribute_write_value(node, call_dispatch_type_for(node)))
|
|
1779
|
+
end
|
|
1780
|
+
|
|
1781
|
+
# Issue #2411 — `recv.each_with_object(memo) { |x, acc| acc[k] = v }` returns the memo as the block's
|
|
1782
|
+
# stores leave it. The statement evaluator owns that join ({StatementEvaluator#each_with_object_value}),
|
|
1783
|
+
# and reading it here gives every value position the statement's answer: a method's tail is typed through
|
|
1784
|
+
# this typer, and read off the dispatch alone it was the memo argument's literal `{}` / `[]`. A `break` ends
|
|
1785
|
+
# the call with its own value, so the block's break arms ({#call_break_arm_types}) join the memo: the join
|
|
1786
|
+
# alone read `break :stop if x == 2; a << x` as `Array[1 | 2]`, where Ruby answers `:stop`.
|
|
1787
|
+
def memo_object_value(node, result)
|
|
1788
|
+
return result unless node.name == :each_with_object && node.block.is_a?(Prism::BlockNode)
|
|
1789
|
+
|
|
1790
|
+
memo = StatementEvaluator.new(scope: scope, tracer: tracer).each_with_object_value(node, result)
|
|
1791
|
+
return memo if memo.equal?(result)
|
|
1792
|
+
|
|
1793
|
+
breaks = call_break_arm_types(node)
|
|
1794
|
+
breaks.empty? ? memo : Type::Combinator.union(memo, *breaks)
|
|
1763
1795
|
end
|
|
1764
1796
|
|
|
1765
1797
|
# The RHS-value override for plain attribute / index writes (#520); a non-write call keeps the
|
|
@@ -2376,14 +2408,12 @@ module Rigor
|
|
|
2376
2408
|
#
|
|
2377
2409
|
# - the receiver is not `Nominal[T]` for some T;
|
|
2378
2410
|
# - no def_node is recorded for that class/method (the receiver is foreign or has only an RBS sig);
|
|
2379
|
-
# - the def has no body, or has a parameter shape we cannot bind from the call's positional args
|
|
2380
|
-
# - the inference is already in progress for this (class, method, signature) tuple — recursion safety
|
|
2381
|
-
# net.
|
|
2411
|
+
# - the def has no body, or has a parameter shape we cannot bind from the call's positional args.
|
|
2382
2412
|
# v0.0.3 A — re-types a top-level (or DSL-block-nested) `def` discovered by `ScopeIndexer` under the
|
|
2383
2413
|
# `TOP_LEVEL_DEF_KEY` sentinel. Mirrors the `infer_user_method_return` shape but uses the current
|
|
2384
2414
|
# `scope.self_type` (or implicit `Object`) as the receiver carrier so the body's own self is
|
|
2385
2415
|
# consistent with the call site's. Returns nil when the parameter shape disqualifies the def, when the
|
|
2386
|
-
# body is empty, or when
|
|
2416
|
+
# body is empty, or when the inference raises.
|
|
2387
2417
|
def infer_top_level_user_method(def_node, receiver, arg_types, block_type = nil)
|
|
2388
2418
|
infer_user_method_return(def_node, receiver, arg_types, yield_type: block_type)
|
|
2389
2419
|
rescue StandardError
|
|
@@ -2430,9 +2460,9 @@ module Rigor
|
|
|
2430
2460
|
|
|
2431
2461
|
# True when `type` is a concrete value — a `Type::Constant` or a `Type::Tuple` whose elements are
|
|
2432
2462
|
# (recursively) all value-pinned. ADR-55 slice 1: a value-pinned self-call result is adopted even
|
|
2433
|
-
# inside a class/method body (where WD3 otherwise keeps non-`Bot` returns as `Dynamic[top]`). A
|
|
2434
|
-
#
|
|
2435
|
-
#
|
|
2463
|
+
# inside a class/method body (where WD3 otherwise keeps non-`Bot` returns as `Dynamic[top]`). A concrete
|
|
2464
|
+
# value at a call site is strictly more precise and can never enable an undefined-method or argument-type
|
|
2465
|
+
# false positive — it is FP-neutral by construction.
|
|
2436
2466
|
def fully_value_pinned?(type)
|
|
2437
2467
|
case type
|
|
2438
2468
|
when Type::Constant then true
|
|
@@ -2634,9 +2664,63 @@ module Rigor
|
|
|
2634
2664
|
found = scope.singleton_def_through_ancestors(class_name, method_name)
|
|
2635
2665
|
owner = found.last
|
|
2636
2666
|
found = [nil, nil] if owner && SingletonExternalAhead.withhold?(scope, class_name, method_name, owner)
|
|
2667
|
+
found = [nil, nil] if found.last && dynamic_singleton_definer_ahead?(class_name, method_name, found.last)
|
|
2637
2668
|
table[key] = found
|
|
2638
2669
|
end
|
|
2639
2670
|
|
|
2671
|
+
SINGLETON_WILDCARD_SLOT = %i[singleton *].freeze
|
|
2672
|
+
private_constant :SINGLETON_WILDCARD_SLOT
|
|
2673
|
+
|
|
2674
|
+
# Issue #2284 — a project entry nearer than the `def`'s owner on the singleton chain may define the name
|
|
2675
|
+
# without a `def`, which the def tables do not hold: `class S < B; define_singleton_method(:x) { "s" }; end`
|
|
2676
|
+
# calls S's block, not `B.x`. The farther `def` is then withheld and the call types `Dynamic`. A class's own
|
|
2677
|
+
# singleton side is read from the visibility-contest census: `define_singleton_method(:x)`,
|
|
2678
|
+
# `singleton_class.define_method(:x)` / `attr_*` and a computed name contest the entry's slot whether or not a
|
|
2679
|
+
# `def` is recorded (#1984, #2013). An `extend`ed module's side is read from `discovered_methods`, which holds
|
|
2680
|
+
# its `attr_*` and `define_method` rows, and the module's `def self.extended` hook may define any name on the
|
|
2681
|
+
# extender, so it withholds too.
|
|
2682
|
+
#
|
|
2683
|
+
# An `inherited` hook anywhere on the chain past the receiver's own entry runs when the receiver's class is
|
|
2684
|
+
# created and may define any singleton method on it (`def self.inherited(s) = s.define_singleton_method(:x)`),
|
|
2685
|
+
# so a `def` the receiver only inherits is withheld then as well: a class's `def self.inherited`, or an
|
|
2686
|
+
# `inherited` an extended module defines.
|
|
2687
|
+
def dynamic_singleton_definer_ahead?(class_name, method_name, owner)
|
|
2688
|
+
name = method_name.to_sym
|
|
2689
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :singleton, :methods)
|
|
2690
|
+
return true if class_name.to_s != owner.to_s && inherited_hook_on_chain?(chain, class_name.to_s)
|
|
2691
|
+
|
|
2692
|
+
chain.entries.each do |entry|
|
|
2693
|
+
next if entry.external?
|
|
2694
|
+
return false if entry.name == owner.to_s
|
|
2695
|
+
return true if entry_defines_without_def?(entry, name)
|
|
2696
|
+
end
|
|
2697
|
+
false
|
|
2698
|
+
end
|
|
2699
|
+
|
|
2700
|
+
def inherited_hook_on_chain?(chain, receiver_name)
|
|
2701
|
+
chain.entries.any? do |entry|
|
|
2702
|
+
next false if entry.external? || entry.name == receiver_name
|
|
2703
|
+
|
|
2704
|
+
if entry.side == :singleton
|
|
2705
|
+
!scope.singleton_def_for(entry.name, :inherited).nil?
|
|
2706
|
+
else
|
|
2707
|
+
scope.discovered_method?(entry.name, :inherited, :instance)
|
|
2708
|
+
end
|
|
2709
|
+
end
|
|
2710
|
+
end
|
|
2711
|
+
|
|
2712
|
+
def entry_defines_without_def?(entry, name)
|
|
2713
|
+
unless entry.side == :singleton
|
|
2714
|
+
return scope.discovered_method?(entry.name, name, :instance) ||
|
|
2715
|
+
!scope.singleton_def_for(entry.name, :extended).nil?
|
|
2716
|
+
end
|
|
2717
|
+
|
|
2718
|
+
discovery = scope.discovery
|
|
2719
|
+
# No edge of its own: `singleton_def_through_ancestors`' chain search filed every entry ahead of the owner.
|
|
2720
|
+
discovery.contested?(:discovered_method_visibilities, [entry.name, [:singleton, name]]) ||
|
|
2721
|
+
discovery.contested?(:discovered_method_visibilities, [entry.name, SINGLETON_WILDCARD_SLOT])
|
|
2722
|
+
end
|
|
2723
|
+
|
|
2640
2724
|
# The project-side singleton-method band: a `Foo.bar` call resolved against a `class << …` / `def self.…`
|
|
2641
2725
|
# body the project itself wrote. Which of the two tiers applies is decided by the receiver carrier —
|
|
2642
2726
|
# `Singleton[Foo]` when the constant names a class or module, `Nominal[…]` when it holds an ordinary
|
|
@@ -2672,7 +2756,7 @@ module Rigor
|
|
|
2672
2756
|
# external entries this lookup passes over. Cross-file: the chain is built from the tables the runner
|
|
2673
2757
|
# seeds from the project-wide pre-pass, cycle-guarded and capped at `Scope::ANCESTOR_WALK_LIMIT`.
|
|
2674
2758
|
|
|
2675
|
-
CLASS_GRAPH_CACHE_KEY = :__rigor_class_graph_cache__
|
|
2759
|
+
CLASS_GRAPH_CACHE_KEY = RunScopedSlots.register(:__rigor_class_graph_cache__)
|
|
2676
2760
|
private_constant :CLASS_GRAPH_CACHE_KEY
|
|
2677
2761
|
|
|
2678
2762
|
# Memo for the static class-graph resolvers below. They are pure functions of the scope's *frozen*
|
|
@@ -2873,7 +2957,7 @@ module Rigor
|
|
|
2873
2957
|
dynamic_top
|
|
2874
2958
|
end
|
|
2875
2959
|
|
|
2876
|
-
OVERRIDE_GATE_CACHE_KEY = :__rigor_overridable_method_gate__
|
|
2960
|
+
OVERRIDE_GATE_CACHE_KEY = RunScopedSlots.register(:__rigor_overridable_method_gate__)
|
|
2877
2961
|
private_constant :OVERRIDE_GATE_CACHE_KEY
|
|
2878
2962
|
|
|
2879
2963
|
# Memo for {#overridden_in_project?}, nested `kind → owner → method_name`. The predicate is a pure
|
|
@@ -2946,7 +3030,7 @@ module Rigor
|
|
|
2946
3030
|
EMPTY_REDEFINERS = [].freeze
|
|
2947
3031
|
private_constant :EMPTY_REDEFINERS
|
|
2948
3032
|
|
|
2949
|
-
METHOD_DEFINERS_INDEX_KEY = :__rigor_method_definers_index__
|
|
3033
|
+
METHOD_DEFINERS_INDEX_KEY = RunScopedSlots.register(:__rigor_method_definers_index__)
|
|
2950
3034
|
private_constant :METHOD_DEFINERS_INDEX_KEY
|
|
2951
3035
|
|
|
2952
3036
|
# The second way. Two flat thread slots rather than one slot holding an array of ways: a
|
|
@@ -2954,7 +3038,7 @@ module Rigor
|
|
|
2954
3038
|
# elements are pinned, and the engine then reads the `way[0].equal?(…)` guards below as always-falsey
|
|
2955
3039
|
# (`make check` fires `flow.always-truthy-condition` on this very file). Two keys read as two plain
|
|
2956
3040
|
# `Thread.current` reads and keep the guards analysable.
|
|
2957
|
-
METHOD_DEFINERS_INDEX_ALT_KEY = :__rigor_method_definers_index_alt__
|
|
3041
|
+
METHOD_DEFINERS_INDEX_ALT_KEY = RunScopedSlots.register(:__rigor_method_definers_index_alt__)
|
|
2958
3042
|
private_constant :METHOD_DEFINERS_INDEX_ALT_KEY
|
|
2959
3043
|
|
|
2960
3044
|
# `method_name (Symbol) → [owner names]` inverted index over the instance / singleton def tables,
|
|
@@ -2988,8 +3072,9 @@ module Rigor
|
|
|
2988
3072
|
# the singleton index.
|
|
2989
3073
|
#
|
|
2990
3074
|
# DELIBERATELY not keyed on the whole {Scope::DiscoveryIndex}, unlike {#class_graph_buckets} and
|
|
2991
|
-
# {#override_gate_buckets} above. {
|
|
2992
|
-
# nothing else, so the def tables are already the complete key, and the index
|
|
3075
|
+
# {#override_gate_buckets} above. {DefinersIndex.for} reads the ONE table it is handed, and the seed that
|
|
3076
|
+
# table was merged over, and nothing else, so the def tables are already the complete key, and the index
|
|
3077
|
+
# object is a strictly
|
|
2993
3078
|
# narrower one: the project-seed scope carries a fresh index per file while its def tables stay the
|
|
2994
3079
|
# same object. Over the 200 synthetic files above, an index-keyed memo misses all 400 times where a
|
|
2995
3080
|
# def-table-keyed one misses 201 — it would rebuild the seed's 934-entry table once per file and hand
|
|
@@ -3007,17 +3092,7 @@ module Rigor
|
|
|
3007
3092
|
Thread.current[METHOD_DEFINERS_INDEX_KEY] = way
|
|
3008
3093
|
end
|
|
3009
3094
|
singleton = kind == :singleton
|
|
3010
|
-
way[singleton ? 3 : 2] ||=
|
|
3011
|
-
end
|
|
3012
|
-
|
|
3013
|
-
def build_method_definers_index(table)
|
|
3014
|
-
index = {}
|
|
3015
|
-
table.each do |class_name, methods|
|
|
3016
|
-
next if class_name == Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY
|
|
3017
|
-
|
|
3018
|
-
methods.each_key { |method_name| (index[method_name] ||= []) << class_name }
|
|
3019
|
-
end
|
|
3020
|
-
index
|
|
3095
|
+
way[singleton ? 3 : 2] ||= DefinersIndex.for(singleton ? singleton_def_nodes : def_nodes)
|
|
3021
3096
|
end
|
|
3022
3097
|
|
|
3023
3098
|
# True when `candidate`'s ancestor chain (superclasses + included/prepended modules) reaches `owner` —
|
|
@@ -3098,10 +3173,11 @@ module Rigor
|
|
|
3098
3173
|
# `[receiver, *args]` descriptor tuple; a callee may appear under at most two node identities per run
|
|
3099
3174
|
# (the project-index parse every OTHER file resolves through, plus the defining file's own analysis
|
|
3100
3175
|
# parse). Only the current generation's bucket is retained (single slot) — a rolled-over run's entries
|
|
3101
|
-
# become garbage instead of accumulating across LSP runs
|
|
3102
|
-
#
|
|
3103
|
-
#
|
|
3104
|
-
|
|
3176
|
+
# become garbage instead of accumulating across LSP runs, and the run that filled the slot releases it
|
|
3177
|
+
# when it ends ({RunScopedSlots}; `Runner#run` keeps its bucket for `#return_summaries`).
|
|
3178
|
+
# `ExpressionTyper` is rebuilt per `Scope#type_of`, so the store lives on `Thread.current`; fork-pool
|
|
3179
|
+
# workers are separate processes, so it never crosses a project boundary (ADR-84 WD5).
|
|
3180
|
+
RETURN_MEMO_KEY = RunScopedSlots.register(:__rigor_user_method_return_memo__)
|
|
3105
3181
|
private_constant :RETURN_MEMO_KEY
|
|
3106
3182
|
|
|
3107
3183
|
# ADR-84 WD2 — one memo entry: the final inferred return plus, when the entry was computed under
|
|
@@ -3318,21 +3394,34 @@ module Rigor
|
|
|
3318
3394
|
cache = class_graph_buckets[:yields]
|
|
3319
3395
|
return cache[def_node] if cache.key?(def_node)
|
|
3320
3396
|
|
|
3321
|
-
cache[def_node] = yield_reachable?(def_node.body)
|
|
3397
|
+
cache[def_node] = yield_reachable?(def_node.body, def_node.parameters&.block&.name)
|
|
3322
3398
|
end
|
|
3323
3399
|
|
|
3324
|
-
def yield_reachable?(node)
|
|
3400
|
+
def yield_reachable?(node, block_name)
|
|
3325
3401
|
return false if node.nil?
|
|
3326
|
-
return true if node.is_a?(Prism::YieldNode)
|
|
3402
|
+
return true if node.is_a?(Prism::YieldNode) || forwards_block_argument?(node, block_name)
|
|
3327
3403
|
|
|
3328
3404
|
node.rigor_each_child do |child|
|
|
3329
3405
|
next if YIELD_BOUNDARY_NODES.include?(child.class)
|
|
3330
3406
|
|
|
3331
|
-
return true if yield_reachable?(child)
|
|
3407
|
+
return true if yield_reachable?(child, block_name)
|
|
3332
3408
|
end
|
|
3333
3409
|
false
|
|
3334
3410
|
end
|
|
3335
3411
|
|
|
3412
|
+
# Issue #1157 — a call that hands on its method's block (`&`, or `&blk` naming the block parameter) reaches the
|
|
3413
|
+
# caller's block as a `yield` does. Whether `blk` is still the method's own block (not rebound or shadowed) is
|
|
3414
|
+
# decided where it is typed ({#forwarded_block_return_type}); this only keeps the call-site dimension for a def
|
|
3415
|
+
# that might.
|
|
3416
|
+
def forwards_block_argument?(node, block_name)
|
|
3417
|
+
block = node.respond_to?(:block) ? node.block : nil
|
|
3418
|
+
return false unless block.is_a?(Prism::BlockArgumentNode)
|
|
3419
|
+
|
|
3420
|
+
expression = block.expression
|
|
3421
|
+
expression.nil? || (!block_name.nil? && expression.is_a?(Prism::LocalVariableReadNode) &&
|
|
3422
|
+
expression.name == block_name)
|
|
3423
|
+
end
|
|
3424
|
+
|
|
3336
3425
|
# The candidate-frame memo path: consult the current run generation's bucket, and on a miss compute
|
|
3337
3426
|
# and store a FINAL result. Reached only when `memo_candidate?` held (see `infer_user_method_return`).
|
|
3338
3427
|
# The store gate (ADR-84 WD3): a result is stored when no transient-machinery event during the
|
|
@@ -3873,7 +3962,7 @@ module Rigor
|
|
|
3873
3962
|
# Consumes one unit from the thread-local unroll-fuel counter and returns the units that were
|
|
3874
3963
|
# available *before* this consumption (so a positive return means the extended value-key may be used).
|
|
3875
3964
|
# Fuel is per-outermost inference entry: at the top level (empty guard stack) it seeds to
|
|
3876
|
-
# `RECURSION_UNROLL_FUEL`, and the `ensure` in `
|
|
3965
|
+
# `RECURSION_UNROLL_FUEL`, and the `ensure` in `evaluate_guarded_user_method_body` clears it once the stack
|
|
3877
3966
|
# drains back to empty. On exhaustion (return 0) it records a `RECURSION_UNROLL_FUEL` hit so the caller
|
|
3878
3967
|
# keeps the plain `(receiver, method)` signature — today's behaviour.
|
|
3879
3968
|
def unroll_fuel_remaining(stack)
|
|
@@ -3936,7 +4025,7 @@ module Rigor
|
|
|
3936
4025
|
# to the receiver's nominal type, the project-wide accumulators inherited (so the body sees the same
|
|
3937
4026
|
# `discovered_classes` / `class_ivars` / etc. the caller does), and the call's `arg_types` bound to
|
|
3938
4027
|
# the parameters. Returns nil when the binding is truly ambiguous (arity mismatch, trailing `post`
|
|
3939
|
-
# params
|
|
4028
|
+
# params).
|
|
3940
4029
|
#
|
|
3941
4030
|
# Issue #524 — the binder is per-PARAMETER, not per-signature. The first iteration declined any def
|
|
3942
4031
|
# with an optional / rest / keyword / block parameter outright, so ONE `options = {}` default zeroed
|
|
@@ -3984,7 +4073,7 @@ module Rigor
|
|
|
3984
4073
|
body_scope.with_repeated_or_writes(RepeatedOrWrites.straight_line_sites(def_node.body, body_scope))
|
|
3985
4074
|
end
|
|
3986
4075
|
|
|
3987
|
-
# Issue #681 / #707 — the
|
|
4076
|
+
# Issue #681 / #707 — the typer's answer to "what `Module.nesting` was recorded for this
|
|
3988
4077
|
# def node". `Scope::DiscoveryIndex#discovered_def_nestings` is the owner; the resolver memo is that
|
|
3989
4078
|
# table's REHYDRATION for the one population it cannot hold — a def served from an ADR-85 seed bundle,
|
|
3990
4079
|
# whose node {Inference::DefNodeResolver} mints from its own parse, so no table keyed by the identities
|
|
@@ -3999,7 +4088,7 @@ module Rigor
|
|
|
3999
4088
|
# run and an `--incremental` recheck answering the same constant for the same body; the rehydration half
|
|
4000
4089
|
# is populated only inside `DefNodeResolver.with_run`, and outside it this falls back to the peel.
|
|
4001
4090
|
def recorded_def_nesting(def_node)
|
|
4002
|
-
scope.discovery.
|
|
4091
|
+
scope.discovery.recorded_def_nesting(def_node)
|
|
4003
4092
|
end
|
|
4004
4093
|
|
|
4005
4094
|
# The body scope's fold-safe set: the body's own struct locals, plus issue #525's `:self` sentinel
|
|
@@ -4328,9 +4417,15 @@ module Rigor
|
|
|
4328
4417
|
|
|
4329
4418
|
# Whether the call may run its block more than once, so a later run reads a captured binding an earlier
|
|
4330
4419
|
# run rebound — the premise of laying the #587 (b) binding ({#captured_block_bindings}) under the block.
|
|
4331
|
-
# {BlockRepetition.may_repeat?} holds the rule, which the statement pass shares (issue #1412).
|
|
4420
|
+
# {BlockRepetition.may_repeat?} holds the rule, which the statement pass shares (issue #1412). A project callee
|
|
4421
|
+
# whose body may run the block more than once on one path ({ProjectYielder.block_value_may_repeat?}) also
|
|
4422
|
+
# counts: a `yield` in a loop or a core iterator's block (`xs.each { |x| v = yield(x) }`, issue #2303), or a
|
|
4423
|
+
# hand-off (`g(x, &)`, issue #2301), whose target may run it many times, so a captured local the block rebinds
|
|
4424
|
+
# must not read its entry binding. A callee that runs it once per path (`f ? yield(1) : yield(2)`,
|
|
4425
|
+
# `then { yield }`) keeps the single-run value.
|
|
4332
4426
|
def block_may_repeat?(call_node, receiver_type)
|
|
4333
|
-
BlockRepetition.may_repeat?(method_name: call_node.name, receiver_type: receiver_type, scope: scope)
|
|
4427
|
+
BlockRepetition.may_repeat?(method_name: call_node.name, receiver_type: receiver_type, scope: scope) ||
|
|
4428
|
+
ProjectYielder.block_value_may_repeat?(call_node, receiver_type, scope)
|
|
4334
4429
|
end
|
|
4335
4430
|
|
|
4336
4431
|
EMPTY_BREAK_ARMS = [].freeze
|
|
@@ -4426,10 +4521,22 @@ module Rigor
|
|
|
4426
4521
|
)
|
|
4427
4522
|
type_block_body(block_arg, entry, captured: captured)
|
|
4428
4523
|
when Prism::BlockArgumentNode
|
|
4429
|
-
symbol_block_return_type(block_arg, expected)
|
|
4524
|
+
forwarded_block_return_type(block_arg) || symbol_block_return_type(block_arg, expected)
|
|
4430
4525
|
end
|
|
4431
4526
|
end
|
|
4432
4527
|
|
|
4528
|
+
# Issue #1157 — `&` / `&blk` hands the callee the block this method was itself called with, so the callee's
|
|
4529
|
+
# `yield` is worth what the CALLER's block returns: the value {.current_yield_value_type} already holds for
|
|
4530
|
+
# this frame (the one a `yield` written here reads). Without it a name resolver that passes its block on
|
|
4531
|
+
# (`def walk(x, &) = first_hit(x, &)`) read as `Dynamic[top]` through the forward and was skipped by
|
|
4532
|
+
# `sig-gen`. Nil when the argument is not a forwarded block or the call site passed no block.
|
|
4533
|
+
def forwarded_block_return_type(block_arg)
|
|
4534
|
+
frame = scope.match_frame
|
|
4535
|
+
return nil unless frame&.forwards_block?(block_arg)
|
|
4536
|
+
|
|
4537
|
+
ExpressionTyper.current_yield_value_type
|
|
4538
|
+
end
|
|
4539
|
+
|
|
4433
4540
|
# The scope a block body is typed under: the surrounding scope plus the parameter bindings the receiving
|
|
4434
4541
|
# method's signature implies, with the #587 (b) `captured` binding ({#repeating_captured_bindings}) laid
|
|
4435
4542
|
# between the two, so the body reads what a captured local, instance variable, class variable or global
|
|
@@ -4524,6 +4631,14 @@ module Rigor
|
|
|
4524
4631
|
# A failure in the re-answer falls back to the plain tail-only answer, for the reason
|
|
4525
4632
|
# {#threaded_block_body_type} gives: a raise reaching `block_return_type_for` would report "no block".
|
|
4526
4633
|
def tail_only_block_body_type(body, block_scope, captured)
|
|
4634
|
+
StatementEvaluator.provisional_returns { tail_only_block_body_type_unrecorded(body, block_scope, captured) }
|
|
4635
|
+
end
|
|
4636
|
+
|
|
4637
|
+
# Issue #2299 — the tail alone, typed from `block_scope`, is not the walk the method's returns are collected
|
|
4638
|
+
# from: a `return` in it reads a name the skipped prefix binds as unbound. Its records are provisional
|
|
4639
|
+
# ({StatementEvaluator.provisional_returns}): the statement walk's record of the same `return` replaces one,
|
|
4640
|
+
# and one stands only for a block the walk never enters (a block in expression position).
|
|
4641
|
+
def tail_only_block_body_type_unrecorded(body, block_scope, captured)
|
|
4527
4642
|
return block_scope.type_of(body) unless block_body_threading_suppressed? && body.is_a?(Prism::StatementsNode)
|
|
4528
4643
|
|
|
4529
4644
|
answered =
|
|
@@ -5520,8 +5635,9 @@ module Rigor
|
|
|
5520
5635
|
end
|
|
5521
5636
|
|
|
5522
5637
|
# Records `type` as the accumulator `block` receives when typed from this typer's scope
|
|
5523
|
-
# ({.recorded_inject_accumulator}).
|
|
5524
|
-
# the
|
|
5638
|
+
# ({.recorded_inject_accumulator}). Prism nodes compare by identity. The keys are weak but the values strong,
|
|
5639
|
+
# and a recorded scope reaches the parse tree that holds the key, so a record never goes on its own (issue
|
|
5640
|
+
# #2412): the map is a run-scoped slot ({RunScopedSlots}), emptied when the run that filled it ends.
|
|
5525
5641
|
def record_inject_accumulator(block, type)
|
|
5526
5642
|
(Thread.current[INJECT_ACCUMULATOR_KEY] ||= ObjectSpace::WeakKeyMap.new)[block] = [scope, type].freeze
|
|
5527
5643
|
end
|
|
@@ -28,13 +28,12 @@ module Rigor
|
|
|
28
28
|
# a flag threaded into `Scope`: an ADR-2 plugin-facing bypass of dependency recording would produce
|
|
29
29
|
# a silently stale warm cache that no diagnostic diff can show.
|
|
30
30
|
#
|
|
31
|
-
# There is deliberately NO memo here.
|
|
32
|
-
# not exist yet, and a memo keyed on the tables this slice happens to name would be keyed on less
|
|
31
|
+
# There is deliberately NO memo here. A memo keyed on the tables the walk names would be keyed on less
|
|
33
32
|
# than the walk reads — the ancestor chain resolves names through `known_user_class?`
|
|
34
33
|
# (`discovered_def_nodes`, `discovered_methods`) and `ancestor_name_candidates`
|
|
35
34
|
# (`discovered_header_nestings`). The chain itself is memoised per discovery index
|
|
36
|
-
# ({Scope::ResolutionChain}); the
|
|
37
|
-
#
|
|
35
|
+
# ({Scope::ResolutionChain}); the dispatch callers in {MethodDispatcher::RbsDispatch} memoise their
|
|
36
|
+
# own answers, keyed on what they read.
|
|
38
37
|
module ExternalAncestorResolution
|
|
39
38
|
# The owner a name must PRECEDE for its declaration to win an MRO. See {declared_before_object?}.
|
|
40
39
|
OBJECT_OWNER = "Object"
|
|
@@ -4,6 +4,8 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../source/constant_path"
|
|
6
6
|
require_relative "../source/node_children"
|
|
7
|
+
require_relative "ivar_writer_census"
|
|
8
|
+
require_relative "stored_block_call"
|
|
7
9
|
|
|
8
10
|
module Rigor
|
|
9
11
|
module Inference
|
|
@@ -26,6 +28,16 @@ module Rigor
|
|
|
26
28
|
# kept apart from `[:defines, …]`.
|
|
27
29
|
# - `[:main_mixin, name]` — a module a top-level `include`, `prepend` or `extend` mixes into `Object` (or
|
|
28
30
|
# `main`), as written. A module RBS does not know may carry any method, so it reaches every class.
|
|
31
|
+
# - the instance-variable writer entries of {IvarWriterCensus} (issue #1154): `[:ivar_set, name]`,
|
|
32
|
+
# `[:ivar_set_class, class_name]`, `[:own_write, class_name, name, path]` and, issue #2079,
|
|
33
|
+
# `[:own_write_type, class_name, name, value, path]`.
|
|
34
|
+
# - `[:global_writer, name, method]` — issue #2314: a write of the global `name` inside a method body, keyed by
|
|
35
|
+
# the `def`'s name (or the literal name a `define_method` block defines), or by nil inside a lambda or a block
|
|
36
|
+
# its call keeps to run later ({StoredBlockCall}). A write in a block its call runs at once belongs to the
|
|
37
|
+
# enclosing body. A write at the top level or in a class body records nothing, and neither does a write of
|
|
38
|
+
# the frame-local `$_` or `$~`. {GlobalWriterReach} reads these.
|
|
39
|
+
# - `[:method_alias, new, old]` — issue #2314: `alias new old`, `alias_method :new, :old` or `def_delegator
|
|
40
|
+
# :@o, :old, :new`, whose new name runs a method named old. {GlobalWriterReach} follows it.
|
|
29
41
|
#
|
|
30
42
|
# The census is a `Set` of those frozen entries, merged by union across files, the project pre-pass and the
|
|
31
43
|
# `pre_eval:` files. It is recorded inside descents that already visit every node ({Collector#visit}), never
|
|
@@ -74,12 +86,41 @@ module Rigor
|
|
|
74
86
|
census.include?(DEFINES_ANY) || names.any? { |name| census.include?([:defines, name]) }
|
|
75
87
|
end
|
|
76
88
|
|
|
89
|
+
# #1154 — the {IvarWriterCensus} entries of `census`, for the incremental diff and its readers.
|
|
90
|
+
IVAR_KINDS = %i[ivar_set ivar_set_class own_write own_write_type].to_set.freeze
|
|
91
|
+
|
|
92
|
+
def ivar_set_entries(census) = census.select { |entry| IVAR_KINDS.include?(entry.first) }.to_set
|
|
93
|
+
|
|
94
|
+
# The negative-dependency key an {IvarWriterCensus} entry fires, the one `UnseenIvarWriters` records.
|
|
95
|
+
def ivar_key(entry)
|
|
96
|
+
case entry.first
|
|
97
|
+
when :ivar_set then "ivar_set:#{entry[1]}"
|
|
98
|
+
when :ivar_set_class then "ivar_set_class:#{entry[1]}"
|
|
99
|
+
when :own_write_type then "own_write_type:#{entry[1]}##{entry[2]}"
|
|
100
|
+
else "own_write:#{entry[1]}##{entry[2]}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
77
104
|
# Whether some refinement may add `write`.
|
|
78
105
|
def refines_write?(census) = census.include?(REFINES_ANY) || census.include?(%i[refines write])
|
|
79
106
|
|
|
80
107
|
# The module names the top-level mixins spell.
|
|
81
108
|
def main_mixins(census) = census.filter_map { |entry| entry[1] if entry[0] == :main_mixin }
|
|
82
109
|
|
|
110
|
+
# Issue #2158 — the `global_census:` dependency key a census entry moves, without the kind prefix: `alias:$x`,
|
|
111
|
+
# `defines:write`, `refines:write`, `defines_any`, `refines_any`, and `main_mixin` for every top-level mixin: its
|
|
112
|
+
# reader asks about each, and one shared key is a deliberate precision trade-off (a mixin moving anywhere
|
|
113
|
+
# re-checks every write that read them). The rules that read the census record these keys
|
|
114
|
+
# (`CheckRules#record_global_census`), and `Analysis::Incremental.changed_global_census_keys` fires them for
|
|
115
|
+
# every entry that appeared in or vanished from a file's census.
|
|
116
|
+
def dependency_key(entry)
|
|
117
|
+
case entry
|
|
118
|
+
when DEFINES_ANY then "defines_any"
|
|
119
|
+
when REFINES_ANY then "refines_any"
|
|
120
|
+
else entry[0] == :main_mixin ? "main_mixin" : "#{entry[0]}:#{entry[1]}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
83
124
|
# A standalone walk, for a file no existing descent visits (a `pre_eval:` entry).
|
|
84
125
|
def scan(root)
|
|
85
126
|
collector = Collector.new(InEffectRefinements.new(root))
|
|
@@ -107,12 +148,18 @@ module Rigor
|
|
|
107
148
|
def initialize(in_effect)
|
|
108
149
|
@census = Set.new
|
|
109
150
|
@refine_ranges = []
|
|
151
|
+
@ivar_writers = IvarWriterCensus.new(@census)
|
|
110
152
|
@in_effect = in_effect
|
|
153
|
+
@bodies = []
|
|
111
154
|
end
|
|
112
155
|
|
|
113
156
|
# A node the collector cannot read must not fail the analysis of its file, nor of every file the pre-pass
|
|
114
157
|
# feeds: the census then counts it as a definition of every name, which only declines a report.
|
|
115
|
-
|
|
158
|
+
# `owner` is the qualified name of the class whose body the node sits in, for {IvarWriterCensus} (nil where
|
|
159
|
+
# the host walk does not track it, or no class owns an instance there).
|
|
160
|
+
def visit(node, top_level:, owner: nil)
|
|
161
|
+
@ivar_writers.visit(node, owner)
|
|
162
|
+
visit_global_writer(node)
|
|
116
163
|
case node
|
|
117
164
|
when Prism::AliasGlobalVariableNode then visit_global_alias(node)
|
|
118
165
|
when Prism::DefNode then record(node, node.name)
|
|
@@ -125,6 +172,68 @@ module Rigor
|
|
|
125
172
|
|
|
126
173
|
private
|
|
127
174
|
|
|
175
|
+
# Issue #2314 — tracks the method bodies, lambdas and stored blocks of a pre-order descent as flat
|
|
176
|
+
# `start, end, label` triples (`@bodies`, innermost last), and records a global write inside one
|
|
177
|
+
# ({GlobalWriterReach}). A body ending before the write starts is closed. Only those nodes read their location.
|
|
178
|
+
def visit_global_writer(node)
|
|
179
|
+
case node
|
|
180
|
+
when Prism::DefNode then open_body(node, node.name)
|
|
181
|
+
when Prism::LambdaNode then open_body(node, nil)
|
|
182
|
+
when Prism::AliasMethodNode then record_method_alias(node.new_name, node.old_name)
|
|
183
|
+
when Prism::CallNode
|
|
184
|
+
open_stored_block(node)
|
|
185
|
+
visit_alias_call(node)
|
|
186
|
+
when Prism::GlobalVariableWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode,
|
|
187
|
+
Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableTargetNode
|
|
188
|
+
record_global_writer(node)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# `$_` and `$~` are the frame's own: a method's write to one never reaches its caller.
|
|
193
|
+
FRAME_LOCAL_GLOBALS = %i[$_ $~].freeze
|
|
194
|
+
private_constant :FRAME_LOCAL_GLOBALS
|
|
195
|
+
|
|
196
|
+
def record_global_writer(node)
|
|
197
|
+
return if FRAME_LOCAL_GLOBALS.include?(node.name)
|
|
198
|
+
|
|
199
|
+
start = node.location.start_offset
|
|
200
|
+
bodies = @bodies
|
|
201
|
+
bodies.pop(3) while !bodies.empty? && bodies[-2] <= start
|
|
202
|
+
index = bodies.size - 3
|
|
203
|
+
index -= 3 while index >= 0 && !(bodies[index] <= start && start < bodies[index + 1])
|
|
204
|
+
@census << [:global_writer, node.name, bodies[index + 2]].freeze if index >= 0
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def open_body(node, label)
|
|
208
|
+
location = node.location
|
|
209
|
+
@bodies.push(location.start_offset, location.end_offset, label)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# `alias_method :new, :old`, and `def_delegator :@o, :old, :new` (and its instance and single spellings), whose
|
|
213
|
+
# new name runs a method of the old name.
|
|
214
|
+
def visit_alias_call(node)
|
|
215
|
+
arguments = node.arguments&.arguments
|
|
216
|
+
case node.name
|
|
217
|
+
when :alias_method then record_method_alias(arguments[0], arguments[1]) if arguments&.size == 2
|
|
218
|
+
when :def_delegator, :def_instance_delegator, :def_single_delegator
|
|
219
|
+
record_method_alias(arguments[2], arguments[1]) if arguments&.size == 3
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def record_method_alias(new_node, old_node)
|
|
224
|
+
new_name = literal_name(new_node)
|
|
225
|
+
old_name = literal_name(old_node)
|
|
226
|
+
@census << [:method_alias, new_name, old_name].freeze if new_name && old_name && new_name != old_name
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def open_stored_block(node)
|
|
230
|
+
block = node.block
|
|
231
|
+
return unless block.is_a?(Prism::BlockNode) && StoredBlockCall.stores_block?(node)
|
|
232
|
+
|
|
233
|
+
definer = %i[define_method define_singleton_method].include?(node.name)
|
|
234
|
+
open_body(block, definer ? literal_name(node.arguments&.arguments&.first) : nil)
|
|
235
|
+
end
|
|
236
|
+
|
|
128
237
|
def visit_global_alias(node)
|
|
129
238
|
[node.new_name, node.old_name].each do |side|
|
|
130
239
|
@census << GlobalWriteCensus.alias_entry(side.name) if side.is_a?(Prism::GlobalVariableReadNode)
|