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
|
@@ -5,6 +5,7 @@ require "prism"
|
|
|
5
5
|
require_relative "../type"
|
|
6
6
|
require_relative "../source/node_children"
|
|
7
7
|
require_relative "return_barrier"
|
|
8
|
+
require_relative "optimistic_origin"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
10
11
|
module Inference
|
|
@@ -21,14 +22,20 @@ module Rigor
|
|
|
21
22
|
#
|
|
22
23
|
# Extracted from `Rigor::SigGen::Generator#infer_return_type` so `LineTypeCollector` (`rigor annotate`'s
|
|
23
24
|
# def-line annotator) and the sig-generator share one source of truth.
|
|
25
|
+
#
|
|
26
|
+
# `keep_lookup_nil: true` is the reading a signature needs (#2197). The engine reads a lookup that can miss
|
|
27
|
+
# (`Hash#[]`, `Array#first`, the {OptimisticOrigin} family) as nil-free on purpose, so `if h[k]` stays
|
|
28
|
+
# silent, but a `-> V` written into `sig/` is a contract that every caller's `nil` check is dead code. Each
|
|
29
|
+
# returned expression whose value is such a lookup therefore contributes `nil` beside its type. Only the
|
|
30
|
+
# value positions a lookup's `nil` reaches the return through are followed ({#optimistic_value?}).
|
|
24
31
|
module DefReturnTyper
|
|
25
32
|
module_function
|
|
26
33
|
|
|
27
|
-
def call(def_node, scope_index)
|
|
34
|
+
def call(def_node, scope_index, keep_lookup_nil: false)
|
|
28
35
|
body = def_node.body
|
|
29
36
|
return nil if body.nil?
|
|
30
37
|
|
|
31
|
-
last =
|
|
38
|
+
last = primary_value_expression(body)
|
|
32
39
|
return nil if last.nil?
|
|
33
40
|
|
|
34
41
|
inner_scope = scope_index[last] || scope_index[body] || scope_index[def_node]
|
|
@@ -37,11 +44,195 @@ module Rigor
|
|
|
37
44
|
last_type = safe_type_of(inner_scope, last)
|
|
38
45
|
return nil if last_type.nil?
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
lookup_context = keep_lookup_nil ? LookupContext.new(scope_index, truthy_guards(body)) : nil
|
|
48
|
+
last_type = with_lookup_nil(last_type, last, inner_scope, lookup_context) if lookup_context
|
|
49
|
+
arm_types = rescue_arm_types(body, inner_scope, scope_index, lookup_context)
|
|
50
|
+
return nil if arm_types.nil?
|
|
51
|
+
|
|
52
|
+
union_with_explicit_returns(body, Type::Combinator.union(last_type, *arm_types), scope_index,
|
|
53
|
+
lookup_context: lookup_context)
|
|
41
54
|
rescue StandardError
|
|
42
55
|
nil
|
|
43
56
|
end
|
|
44
57
|
|
|
58
|
+
# Issue #2410 — the value a def body ends on when no exception escapes its primary path. A def-level
|
|
59
|
+
# `rescue … else … end` body is a `BeginNode` whose else-clause, when present, replaces the body's value (the
|
|
60
|
+
# ensure-clause's value is discarded, so it never counts).
|
|
61
|
+
def primary_value_expression(body)
|
|
62
|
+
return body_last_expression(body) unless body.is_a?(Prism::BeginNode) && body.else_clause
|
|
63
|
+
|
|
64
|
+
body_last_expression(body.else_clause.statements)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Issue #2410 — the value of each arm of a def-level rescue chain, which the method returns when that arm
|
|
68
|
+
# completes. An empty arm returns `nil`; an arm ending in `raise`, `retry` or `return` types as bottom and so
|
|
69
|
+
# adds nothing here (a `return`'s value is collected with the other explicit returns). Nil when an arm cannot
|
|
70
|
+
# be typed: the body's value alone would be a narrower, wrong contract.
|
|
71
|
+
def rescue_arm_types(body, inner_scope, scope_index, lookup_context)
|
|
72
|
+
return [] unless body.is_a?(Prism::BeginNode)
|
|
73
|
+
|
|
74
|
+
types = []
|
|
75
|
+
arm = body.rescue_clause
|
|
76
|
+
while arm
|
|
77
|
+
type = rescue_arm_type(arm, inner_scope, scope_index, lookup_context)
|
|
78
|
+
return nil if type.nil?
|
|
79
|
+
|
|
80
|
+
types << type
|
|
81
|
+
arm = arm.subsequent
|
|
82
|
+
end
|
|
83
|
+
types
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def rescue_arm_type(arm, inner_scope, scope_index, lookup_context)
|
|
87
|
+
value = body_last_expression(arm.statements)
|
|
88
|
+
return Type::Combinator.constant_of(nil) if value.nil?
|
|
89
|
+
|
|
90
|
+
scope = scope_index[value] || scope_index[arm] || inner_scope
|
|
91
|
+
type = safe_type_of(scope, value)
|
|
92
|
+
return nil if type.nil?
|
|
93
|
+
|
|
94
|
+
lookup_context ? with_lookup_nil(type, value, scope, lookup_context) : type
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# What {#optimistic_value?} reads beside a node's scope: the engine's per-node scopes, and for each lookup
|
|
98
|
+
# under a guard the source of the guarding predicates ({#truthy_guards}).
|
|
99
|
+
LookupContext = Data.define(:scope_index, :guards)
|
|
100
|
+
|
|
101
|
+
def with_lookup_nil(type, node, scope, context = nil)
|
|
102
|
+
return type unless optimistic_value?(node, scope, context)
|
|
103
|
+
|
|
104
|
+
Type::Combinator.union(type, Type::Combinator.constant_of(nil))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Whether `node`'s value can be the `nil` an optimistic lookup's miss returns: the lookup itself (a call
|
|
108
|
+
# the dispatcher marked, or `recv&.m` over one), a local or ivar bound to one, or a branch or operand that
|
|
109
|
+
# hands its value on. `a && b` hands on either operand (a falsy `a`, a missed lookup's `nil` included, is
|
|
110
|
+
# the result); `a || b` hands on `b` only, since `a` is returned only when truthy, so `h[k] || 0` stays
|
|
111
|
+
# nil-free. `x ||= v` and `x &&= v` hand on `v` and, for `&&=`, the binding. A call ON the lookup
|
|
112
|
+
# (`h[k].to_s`) is a different value.
|
|
113
|
+
#
|
|
114
|
+
# Each child is read under the scope the engine recorded for it (`scope_index`), so a branch a guard narrows
|
|
115
|
+
# (`v ? v : "d"`, `return "z" unless v`) reads the binding the narrowing left, not the enclosing one.
|
|
116
|
+
def optimistic_value?(node, scope, context = nil)
|
|
117
|
+
scope = context&.scope_index&.[](node) || scope
|
|
118
|
+
case node
|
|
119
|
+
when Prism::CallNode then optimistic_call?(node, scope, context)
|
|
120
|
+
when Prism::LocalVariableReadNode then !scope.optimistic_local(node.name).nil?
|
|
121
|
+
when Prism::InstanceVariableReadNode then !scope.optimistic_ivar(node.name).nil?
|
|
122
|
+
when Prism::AndNode then [node.left, node.right].any? { optimistic_value?(it, scope, context) }
|
|
123
|
+
when Prism::OrNode then optimistic_value?(node.right, scope, context)
|
|
124
|
+
else optimistic_write?(node, scope, context) || optimistic_branch?(node, scope, context)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def optimistic_call?(node, scope, context)
|
|
129
|
+
if scope.optimistic_origins[node]
|
|
130
|
+
return false if guarded_lookup?(node, context) || non_empty_end?(node, scope)
|
|
131
|
+
|
|
132
|
+
return true
|
|
133
|
+
end
|
|
134
|
+
node.safe_navigation? && optimistic_value?(node.receiver, scope, context)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# `return h[k] if h[k]`, `h[k] ? h[k] : d`: the same lookup, read again under a predicate that just found it
|
|
138
|
+
# truthy. Only a lookup whose receiver and arguments are plain reads or literals counts, so the second read
|
|
139
|
+
# sees what the first did.
|
|
140
|
+
GUARDABLE_LOOKUPS = %i[[] first last dig].freeze
|
|
141
|
+
private_constant :GUARDABLE_LOOKUPS
|
|
142
|
+
|
|
143
|
+
def guarded_lookup?(node, context)
|
|
144
|
+
return false if context.nil? || !GUARDABLE_LOOKUPS.include?(node.name)
|
|
145
|
+
return false unless [node.receiver, *node.arguments&.arguments].all? { stable_operand?(it) }
|
|
146
|
+
|
|
147
|
+
context.guards.fetch(node, EMPTY_GUARDS).include?(node.slice)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
EMPTY_GUARDS = [].freeze
|
|
151
|
+
private_constant :EMPTY_GUARDS
|
|
152
|
+
|
|
153
|
+
def stable_operand?(node)
|
|
154
|
+
case node
|
|
155
|
+
when Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode, Prism::SymbolNode, Prism::StringNode,
|
|
156
|
+
Prism::IntegerNode, Prism::SelfNode, Prism::ConstantReadNode then true
|
|
157
|
+
else false
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# `a.first` / `a.last` on a receiver the engine has narrowed to a non-empty array (`a.empty? ? d : a.first`)
|
|
162
|
+
# cannot miss.
|
|
163
|
+
def non_empty_end?(node, scope)
|
|
164
|
+
return false unless %i[first last].include?(node.name) && node.arguments.nil? && node.receiver
|
|
165
|
+
|
|
166
|
+
receiver = scope.type_of(node.receiver)
|
|
167
|
+
receiver.is_a?(Type::Difference) && receiver.removed == Type::Combinator.tuple_of
|
|
168
|
+
rescue StandardError
|
|
169
|
+
false
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def optimistic_write?(node, scope, context)
|
|
173
|
+
case node
|
|
174
|
+
when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode,
|
|
175
|
+
Prism::LocalVariableOrWriteNode, Prism::InstanceVariableOrWriteNode
|
|
176
|
+
optimistic_value?(node.value, scope, context)
|
|
177
|
+
when Prism::LocalVariableAndWriteNode
|
|
178
|
+
!scope.optimistic_local(node.name).nil? || optimistic_value?(node.value, scope, context)
|
|
179
|
+
when Prism::InstanceVariableAndWriteNode
|
|
180
|
+
!scope.optimistic_ivar(node.name).nil? || optimistic_value?(node.value, scope, context)
|
|
181
|
+
else false
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The value-carrying children of a statement list or a conditional: any of them can be the value returned.
|
|
186
|
+
def optimistic_branch?(node, scope, context)
|
|
187
|
+
value_branches(node).any? { |branch| !branch.nil? && optimistic_value?(branch, scope, context) }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# The lookup a truthy arm (the body of an `if` or ternary, the `else` of an `unless`) reads FIRST, mapped to
|
|
191
|
+
# the source of the predicate that just tested it, for {#guarded_lookup?}. Only the first expression, or the
|
|
192
|
+
# value of a first `return`, counts: anything run between the test and the re-read (`h.delete(k)`, a
|
|
193
|
+
# reassignment, a block, `yield`, another call that mutates the receiver) can make the re-read miss.
|
|
194
|
+
def truthy_guards(body)
|
|
195
|
+
guards = {}.compare_by_identity
|
|
196
|
+
collect_truthy_guards(body, guards)
|
|
197
|
+
guards
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def collect_truthy_guards(node, guards)
|
|
201
|
+
return unless node.is_a?(Prism::Node)
|
|
202
|
+
|
|
203
|
+
predicate, arm = truthy_arm(node)
|
|
204
|
+
first = first_value(arm)
|
|
205
|
+
guards[first] = [predicate.slice] if first
|
|
206
|
+
node.rigor_each_child { collect_truthy_guards(it, guards) }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def first_value(arm)
|
|
210
|
+
return nil unless arm.is_a?(Prism::StatementsNode)
|
|
211
|
+
|
|
212
|
+
first = arm.body.first
|
|
213
|
+
args = first.is_a?(Prism::ReturnNode) ? first.arguments&.arguments : nil
|
|
214
|
+
args&.size == 1 ? args.first : first
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def truthy_arm(node)
|
|
218
|
+
case node
|
|
219
|
+
when Prism::IfNode then [node.predicate, node.statements]
|
|
220
|
+
when Prism::UnlessNode then [node.predicate, node.else_clause&.statements]
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def value_branches(node)
|
|
225
|
+
case node
|
|
226
|
+
when Prism::ParenthesesNode then [node.body && body_last_expression(node.body)]
|
|
227
|
+
when Prism::StatementsNode, Prism::BeginNode then [body_last_expression(node)]
|
|
228
|
+
when Prism::IfNode then [node.statements, node.subsequent]
|
|
229
|
+
when Prism::UnlessNode then [node.statements, node.else_clause]
|
|
230
|
+
when Prism::ElseNode, Prism::WhenNode, Prism::InNode then [node.statements]
|
|
231
|
+
when Prism::CaseNode, Prism::CaseMatchNode then [*node.conditions, node.else_clause]
|
|
232
|
+
else []
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
45
236
|
def body_last_expression(body)
|
|
46
237
|
case body
|
|
47
238
|
when Prism::StatementsNode then body.body.last
|
|
@@ -50,27 +241,27 @@ module Rigor
|
|
|
50
241
|
end
|
|
51
242
|
end
|
|
52
243
|
|
|
53
|
-
def union_with_explicit_returns(body, last_type, scope_index)
|
|
244
|
+
def union_with_explicit_returns(body, last_type, scope_index, lookup_context: nil)
|
|
54
245
|
return_types = []
|
|
55
|
-
collect_return_types(body, scope_index, return_types)
|
|
246
|
+
collect_return_types(body, scope_index, return_types, lookup_context: lookup_context)
|
|
56
247
|
return last_type if return_types.empty?
|
|
57
248
|
|
|
58
249
|
Type::Combinator.union(last_type, *return_types)
|
|
59
250
|
end
|
|
60
251
|
|
|
61
|
-
def collect_return_types(node, scope_index, out)
|
|
252
|
+
def collect_return_types(node, scope_index, out, lookup_context: nil)
|
|
62
253
|
return unless node.is_a?(Prism::Node)
|
|
63
254
|
return if ReturnBarrier.node?(node)
|
|
64
255
|
|
|
65
|
-
type_return_node(node, scope_index, out) if node.is_a?(Prism::ReturnNode)
|
|
256
|
+
type_return_node(node, scope_index, out, lookup_context: lookup_context) if node.is_a?(Prism::ReturnNode)
|
|
66
257
|
# A barrier call's receiver and arguments still run in the method; only its block is cut off.
|
|
67
258
|
barrier_block = node.block if node.is_a?(Prism::CallNode) && ReturnBarrier.block_call?(node)
|
|
68
259
|
node.rigor_each_child do |c|
|
|
69
|
-
collect_return_types(c, scope_index, out) unless c.equal?(barrier_block)
|
|
260
|
+
collect_return_types(c, scope_index, out, lookup_context: lookup_context) unless c.equal?(barrier_block)
|
|
70
261
|
end
|
|
71
262
|
end
|
|
72
263
|
|
|
73
|
-
def type_return_node(return_node, scope_index, out)
|
|
264
|
+
def type_return_node(return_node, scope_index, out, lookup_context: nil)
|
|
74
265
|
args = return_node.arguments&.arguments || []
|
|
75
266
|
if args.empty?
|
|
76
267
|
out << Type::Combinator.constant_of(nil)
|
|
@@ -79,12 +270,13 @@ module Rigor
|
|
|
79
270
|
|
|
80
271
|
scope = scope_index[return_node] || scope_index[args.first]
|
|
81
272
|
return if scope.nil?
|
|
82
|
-
# `return a, b` packs into a Tuple at runtime;
|
|
83
|
-
# returns contribute no type to keep the implementation focused.
|
|
273
|
+
# `return a, b` packs into a Tuple at runtime; only the single-value form is typed.
|
|
84
274
|
return unless args.size == 1
|
|
85
275
|
|
|
86
276
|
type = safe_type_of(scope, args.first)
|
|
87
|
-
|
|
277
|
+
return if type.nil?
|
|
278
|
+
|
|
279
|
+
out << (lookup_context ? with_lookup_nil(type, args.first, scope, lookup_context) : type)
|
|
88
280
|
end
|
|
89
281
|
|
|
90
282
|
def safe_type_of(scope, node)
|
|
@@ -25,8 +25,10 @@ module Rigor
|
|
|
25
25
|
# truth-tested: {UNKNOWN} and {ABSENT} are both truthy, so any other use folds a decline into a firing arm.
|
|
26
26
|
# `spec/rigor/inference/definer_resolution_case_in_spec.rb` fails on a call site of any other shape.
|
|
27
27
|
#
|
|
28
|
-
# It migrates one question at a time: `SourceArity` (C1b)
|
|
29
|
-
#
|
|
28
|
+
# It migrates one question at a time: `SourceArity` (C1b, C2-c) asks for the receiver's own side, instance or
|
|
29
|
+
# singleton; the override lints (C1c) and the C2 typing sites (`ExpressionTyper#resolve_typing_def_with_owner`,
|
|
30
|
+
# `StatementEvaluator#own_definer_node`) read the instance side; `RefinedDispatch::OwnMethod.certain_owner?` and
|
|
31
|
+
# `CheckRules#reached_definer_visibility` pass the side they hold.
|
|
30
32
|
module DefinerResolution
|
|
31
33
|
Known = Data.define(:answer, :owner)
|
|
32
34
|
UnknownResult = Data.define
|
|
@@ -221,11 +223,15 @@ module Rigor
|
|
|
221
223
|
# alone where a later `attr_*` replaced the `def`: the attr writes the name's visibility slot, so the reader's
|
|
222
224
|
# visibility is certain. A `define_method` or alias that replaced it contests the visibility slot as well, which
|
|
223
225
|
# this question does read.
|
|
226
|
+
#
|
|
227
|
+
# Issue #2134 — the contest is a read of the slot, so a question that reads it files `visibility:<owner>#<name>`:
|
|
228
|
+
# an answer block may answer from RBS without reading the slot itself (`nearest_ancestor_method_def`).
|
|
224
229
|
def possible?(scope, hit, method_name, reads_visibility)
|
|
225
230
|
discovery = scope.discovery
|
|
226
231
|
owner = hit.owner
|
|
227
232
|
kind = hit.side == :singleton ? :singleton : :instance
|
|
228
233
|
nodes = kind == :singleton ? :discovered_singleton_def_nodes : :discovered_def_nodes
|
|
234
|
+
Analysis::DependencyRecorder.read_visibility(owner, method_name) if reads_visibility
|
|
229
235
|
discovery.possible_method?(owner, method_name, kind) ||
|
|
230
236
|
(!(reads_visibility == :visibility && kind == :instance) &&
|
|
231
237
|
discovery.contested?(nodes, [owner, method_name.to_sym])) ||
|
|
@@ -277,7 +283,7 @@ module Rigor
|
|
|
277
283
|
|
|
278
284
|
def side_visibility(scope, owner, method_name, kind)
|
|
279
285
|
if kind == :singleton
|
|
280
|
-
scope.
|
|
286
|
+
scope.discovered_method_visibility(owner, method_name, singleton: true) ||
|
|
281
287
|
self_extended_copy_visibility(scope, owner, method_name)
|
|
282
288
|
else
|
|
283
289
|
scope.discovered_method_visibility(owner, method_name)
|
|
@@ -301,11 +307,21 @@ module Rigor
|
|
|
301
307
|
|
|
302
308
|
# Whether any file's deferred ranges carry a `module_function` row (`:both` or `:singleton`) for the name on
|
|
303
309
|
# `owner` (`ModuleFunctionState`).
|
|
310
|
+
#
|
|
311
|
+
# Issue #2161 — the answer reads every file's rows, so it records `visibility:<owner>#<name>` whichever way it
|
|
312
|
+
# answers: `ScopeIndexer.visibility_entries` carries each file's rows of this shape, so a file that starts or
|
|
313
|
+
# stops writing one fires the key. A swap from `private :x` to `module_function :x` in a reopening leaves the
|
|
314
|
+
# file's visibility table and declared classes as they were, and fires nothing else. The caller's own
|
|
315
|
+
# visibility reads record the same key today; this one keeps the reader's edge where it reads.
|
|
316
|
+
#
|
|
317
|
+
# Issue #2209 — a row named {ModuleFunctionState::ANY_ROW_NAME} (a `module_function` spelling whose names the
|
|
318
|
+
# prescan cannot read) stands for every name of its owner, so it counts too, and its key is read as well
|
|
319
|
+
# ({ModuleFunctionState.row_names}).
|
|
304
320
|
def module_function_row?(scope, owner, method_name)
|
|
305
|
-
|
|
321
|
+
names = ModuleFunctionState.row_names(method_name).each { Analysis::DependencyRecorder.read_visibility(owner, it) }
|
|
306
322
|
scope.discovered_deferred_ranges.each_value.any? do |rows|
|
|
307
323
|
rows.any? do |(_start, _finish, row_name, kind, row_owner)|
|
|
308
|
-
row_name
|
|
324
|
+
names.include?(row_name) && row_owner == owner.to_s && kind != :instance
|
|
309
325
|
end
|
|
310
326
|
end
|
|
311
327
|
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../run_scoped_slots"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
# The `method_name (Symbol) → [owner names]` inverted index the override gate reads
|
|
8
|
+
# (`ExpressionTyper#method_definers_index`), over one instance or singleton def table.
|
|
9
|
+
#
|
|
10
|
+
# Issue #2300 — a file's def table is its own `def`s merged over the cross-file seed
|
|
11
|
+
# (`ScopeIndexer#merge_def_node_tables`, `#merge_singleton_def_nodes`, then the #526 extend fold), a new Hash per
|
|
12
|
+
# file. Inverting it whole scanned every project `def` once per analysed file. {.record_layers} remembers the
|
|
13
|
+
# seed a final merged table was built over, and {.for} then inverts the seed ONCE per seed table and, per file,
|
|
14
|
+
# only the owners whose inner table is not the seed's own object ({Layered}).
|
|
15
|
+
#
|
|
16
|
+
# The two answers hold the same owners: an owner the merge left alone shares the seed's inner table, so its
|
|
17
|
+
# names are the seed's, and every other owner is read from the merged table itself. That holds only while the
|
|
18
|
+
# seed cannot change under the cached index, so a seed whose tables are not all frozen, or a merged table
|
|
19
|
+
# missing one of the seed's owners, is inverted whole, as is a table {.record_layers} was not told about. Only
|
|
20
|
+
# the order of a name's owners can differ, and the gate asks `any?` of the list.
|
|
21
|
+
module DefinersIndex
|
|
22
|
+
# These thread slots are bounded, like `ExpressionTyper#method_definers_index`'s two ways. Each is a fixed
|
|
23
|
+
# number of entries, replaced rather than accumulated: two `[merged, seed]` pairs (the last file's two def
|
|
24
|
+
# tables) and two seed indexes. The run that filled them releases them when it ends ({RunScopedSlots}, #2399),
|
|
25
|
+
# so a finished run keeps none of its seed tables or their indexes alive.
|
|
26
|
+
LAYERS_KEY = RunScopedSlots.register(:__rigor_definers_index_layers__)
|
|
27
|
+
SEED_INDEX_KEY = RunScopedSlots.register(:__rigor_definers_index_seed__)
|
|
28
|
+
SEED_INDEX_ALT_KEY = RunScopedSlots.register(:__rigor_definers_index_seed_alt__)
|
|
29
|
+
private_constant :LAYERS_KEY, :SEED_INDEX_KEY, :SEED_INDEX_ALT_KEY
|
|
30
|
+
|
|
31
|
+
# A name's owners: the seed's, less the owners the file replaced, plus the replaced owners' own.
|
|
32
|
+
class Layered
|
|
33
|
+
def initialize(seed_index, replaced, replaced_index)
|
|
34
|
+
@seed_index = seed_index
|
|
35
|
+
@replaced = replaced
|
|
36
|
+
@replaced_index = replaced_index
|
|
37
|
+
@answers = {}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def [](method_name)
|
|
41
|
+
return @answers[method_name] if @answers.key?(method_name)
|
|
42
|
+
|
|
43
|
+
from_seed = @seed_index[method_name]
|
|
44
|
+
from_seed = from_seed.reject { |owner| @replaced.key?(owner) } if from_seed && !@replaced.empty?
|
|
45
|
+
from_file = @replaced_index[method_name]
|
|
46
|
+
owners = from_file.nil? ? from_seed : (from_seed || []) + from_file
|
|
47
|
+
@answers[method_name] = owners.nil? || owners.empty? ? nil : owners
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module_function
|
|
52
|
+
|
|
53
|
+
# Records that the final `merged` table was built over `seed`. The last two are kept (a file merges an
|
|
54
|
+
# instance and a singleton table), replaced rather than accumulated.
|
|
55
|
+
def record_layers(merged, seed)
|
|
56
|
+
previous = Thread.current[LAYERS_KEY]
|
|
57
|
+
Thread.current[LAYERS_KEY] = [[merged, seed], previous&.first].compact
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The definers index over `table`.
|
|
61
|
+
def for(table)
|
|
62
|
+
seed = Thread.current[LAYERS_KEY]&.find { |entry| entry[0].equal?(table) }&.last
|
|
63
|
+
return build(table) if seed.nil? || !deeply_frozen?(seed)
|
|
64
|
+
|
|
65
|
+
replaced = {}
|
|
66
|
+
kept = 0
|
|
67
|
+
table.each do |owner, methods|
|
|
68
|
+
if methods.equal?(seed[owner])
|
|
69
|
+
kept += 1
|
|
70
|
+
else
|
|
71
|
+
replaced[owner] = methods
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
return build(table) unless kept + replaced.count { |owner, _| seed.key?(owner) } == seed.size
|
|
75
|
+
|
|
76
|
+
Layered.new(seed_index(seed), replaced, build(replaced))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def deeply_frozen?(seed)
|
|
80
|
+
seed.frozen? && seed.each_value.all?(&:frozen?)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The seed's index, built once per seed table. Two ways, an instance and a singleton seed table.
|
|
84
|
+
def seed_index(seed)
|
|
85
|
+
[SEED_INDEX_KEY, SEED_INDEX_ALT_KEY].each do |key|
|
|
86
|
+
slot = Thread.current[key]
|
|
87
|
+
return slot[1] if slot && slot[0].equal?(seed)
|
|
88
|
+
end
|
|
89
|
+
index = build(seed)
|
|
90
|
+
Thread.current[SEED_INDEX_ALT_KEY] = Thread.current[SEED_INDEX_KEY]
|
|
91
|
+
Thread.current[SEED_INDEX_KEY] = [seed, index]
|
|
92
|
+
index
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def build(table)
|
|
96
|
+
index = {}
|
|
97
|
+
table.each do |class_name, methods|
|
|
98
|
+
next if class_name == ScopeIndexer::TOP_LEVEL_DEF_KEY
|
|
99
|
+
|
|
100
|
+
methods.each_key { |method_name| (index[method_name] ||= []) << class_name }
|
|
101
|
+
end
|
|
102
|
+
index
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|