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
|
@@ -53,6 +53,24 @@ module Rigor
|
|
|
53
53
|
# `else`-only.
|
|
54
54
|
DEFENSIVE_EXIT_CALLS = %i[raise fail throw abort exit].freeze
|
|
55
55
|
|
|
56
|
+
# Issue #2071 — core classes whose idiomatic stand-ins are not their subclasses: a `StringIO` answers the
|
|
57
|
+
# IO protocol without being an `IO`, and a `Tempfile` delegates to a `File` without being one (or an `IO`).
|
|
58
|
+
# A subject Rigor types as one of these classes (`$stdout` by idiom, ADR-117) may hold such a stand-in,
|
|
59
|
+
# which no `when IO` / `in File` matches, so the `else` still runs for it.
|
|
60
|
+
DUCK_STAND_INS = {
|
|
61
|
+
"IO" => %w[StringIO Tempfile].freeze,
|
|
62
|
+
"File" => %w[Tempfile].freeze
|
|
63
|
+
}.freeze
|
|
64
|
+
|
|
65
|
+
# Each stand-in's ancestors as Ruby 4.0.5 has them (`StringIO.ancestors`, `Tempfile.ancestors` without the
|
|
66
|
+
# anonymous `DelegateClass` entries): a clause naming one matches the stand-in. Written out rather than read
|
|
67
|
+
# from RBS, which declares `class Tempfile < File`; a `Tempfile` is a `Delegator`, so neither `Object` nor
|
|
68
|
+
# `Kernel` matches it.
|
|
69
|
+
STAND_IN_ANCESTORS = {
|
|
70
|
+
"StringIO" => %w[StringIO Enumerable Object Kernel BasicObject].freeze,
|
|
71
|
+
"Tempfile" => %w[Tempfile Delegator BasicObject].freeze
|
|
72
|
+
}.freeze
|
|
73
|
+
|
|
56
74
|
# @!attribute kind
|
|
57
75
|
# @return `:disjoint` (this clause's type is disjoint
|
|
58
76
|
# from the still-possible subject), `:prior_exhaustion` (an
|
|
@@ -137,9 +155,42 @@ module Rigor
|
|
|
137
155
|
end
|
|
138
156
|
coverage.record(clause)
|
|
139
157
|
end
|
|
158
|
+
return if stand_in_reaches_else?(node, entry_type)
|
|
159
|
+
|
|
140
160
|
collect_else(node.else_clause, subject.name, keyword)
|
|
141
161
|
end
|
|
142
162
|
|
|
163
|
+
# Issue #2071 — whether the subject, as the `case` binds it, has a `Nominal` member of a {DUCK_STAND_INS}
|
|
164
|
+
# class with a stand-in no clause names an ancestor of ({STAND_IN_ANCESTORS}). `io = STDOUT; case io when IO
|
|
165
|
+
# then … else … end` runs the `else` for a `StringIO`, which the type `IO` stands for in idiomatic Ruby; a
|
|
166
|
+
# literal subject is a `Constant`, not a `Nominal`, and keeps its verdict. Asked of the `else` only: a dead
|
|
167
|
+
# `when` has its own evidence rules (#1429, #1485).
|
|
168
|
+
def stand_in_reaches_else?(node, entry_type)
|
|
169
|
+
members = entry_type.is_a?(Type::Union) ? entry_type.members : [entry_type]
|
|
170
|
+
stand_ins = members.flat_map do |member|
|
|
171
|
+
member.is_a?(Type::Nominal) ? DUCK_STAND_INS.fetch(member.class_name.delete_prefix("::"), []) : []
|
|
172
|
+
end
|
|
173
|
+
return false if stand_ins.empty?
|
|
174
|
+
|
|
175
|
+
named = clause_class_names(node)
|
|
176
|
+
stand_ins.any? { |stand_in| named.none? { |name| STAND_IN_ANCESTORS.fetch(stand_in).include?(name) } }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# The classes the clauses name as bare constants (`when A, B`, `in A`, `in A => x`).
|
|
180
|
+
def clause_class_names(node)
|
|
181
|
+
node.conditions.flat_map do |clause|
|
|
182
|
+
patterns = clause.is_a?(Prism::WhenNode) ? clause.conditions : [bare_pattern(clause.pattern)]
|
|
183
|
+
patterns.filter_map do |pattern|
|
|
184
|
+
entry = @scope_index[pattern]
|
|
185
|
+
entry && Inference::Narrowing.lexical_class_name(pattern, entry)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def bare_pattern(pattern)
|
|
191
|
+
pattern.is_a?(Prism::CapturePatternNode) ? pattern.value : pattern
|
|
192
|
+
end
|
|
193
|
+
|
|
143
194
|
def entry_subject_type(case_node, subject_name)
|
|
144
195
|
scope = @scope_index[case_node]
|
|
145
196
|
scope&.local(subject_name)
|
|
@@ -229,8 +280,8 @@ module Rigor
|
|
|
229
280
|
|
|
230
281
|
# The trailing `else` is dead when every subject value is covered by the `when` clauses — the final
|
|
231
282
|
# falsey scope (recorded on the `else` node) narrows the subject to `bot`. Defensive `else` bodies (a
|
|
232
|
-
# bare `raise` / `fail` / `throw`) are deliberate guards, not removable dead code, so
|
|
233
|
-
# skipped.
|
|
283
|
+
# bare `raise` / `fail` / `throw` / `abort` / `exit`) are deliberate guards, not removable dead code, so
|
|
284
|
+
# they are skipped.
|
|
234
285
|
def collect_else(else_clause, subject_name, keyword)
|
|
235
286
|
return if else_clause.nil? || else_clause.statements.nil?
|
|
236
287
|
return if defensive_else?(else_clause)
|
|
@@ -29,7 +29,7 @@ module Rigor
|
|
|
29
29
|
class VoidValueUseCollector
|
|
30
30
|
# The assignment forms whose `value` slot is a value position. `MultiWriteNode` (destructuring) is
|
|
31
31
|
# intentionally excluded — its `value` is a container the void call would have to be spread through,
|
|
32
|
-
# not a direct use, so it is
|
|
32
|
+
# not a direct use, so it is outside the FP-narrow scope.
|
|
33
33
|
WRITE_NODE_CLASSES = [
|
|
34
34
|
Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode,
|
|
35
35
|
Prism::ClassVariableWriteNode, Prism::GlobalVariableWriteNode,
|
|
@@ -29,9 +29,11 @@ require_relative "check_rules/void_value_use_collector"
|
|
|
29
29
|
require_relative "check_rules/self_closedness_scanner"
|
|
30
30
|
require_relative "check_rules/source_arity"
|
|
31
31
|
require_relative "check_rules/lexical_method_sites"
|
|
32
|
+
require_relative "check_rules/rebinding_block_callees"
|
|
32
33
|
require_relative "check_rules/rbs_receiver_surface"
|
|
33
34
|
require_relative "check_rules/special_global_setters"
|
|
34
35
|
require_relative "check_rules/ruby_deprecations"
|
|
36
|
+
require_relative "../run_scoped_slots"
|
|
35
37
|
|
|
36
38
|
module Rigor
|
|
37
39
|
module Analysis
|
|
@@ -52,7 +54,7 @@ module Rigor
|
|
|
52
54
|
#
|
|
53
55
|
# - implicit-self calls (no `node.receiver`) — too noisy
|
|
54
56
|
# without per-method RBS for every helper in the class.
|
|
55
|
-
# - dynamic / unknown receivers (`Dynamic[T]`, `Top
|
|
57
|
+
# - dynamic / unknown receivers (`Dynamic[T]`, `Top`)
|
|
56
58
|
# — by definition we cannot enumerate the method set.
|
|
57
59
|
# - shape carriers: `Tuple` → "Array", `HashShape` → "Hash",
|
|
58
60
|
# `Constant` → the constant's class — `concrete_class_name`
|
|
@@ -85,9 +87,8 @@ module Rigor
|
|
|
85
87
|
|
|
86
88
|
module_function
|
|
87
89
|
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# have already produced `scope_index` through
|
|
90
|
+
# Returns the diagnostics for `root`'s subtree across the rule
|
|
91
|
+
# catalogue. The caller MUST have already produced `scope_index` through
|
|
91
92
|
# `Rigor::Inference::ScopeIndexer.index(root, default_scope:)`.
|
|
92
93
|
#
|
|
93
94
|
# @param path — used to populate
|
|
@@ -450,11 +451,31 @@ module Rigor
|
|
|
450
451
|
other_class = ivar_class_for(write[:type])
|
|
451
452
|
next nil if other_class.nil? || other_class == "NilClass" || other_class == first_class
|
|
452
453
|
next nil if declaration_admits?(write[:declared], canonical[:type], write[:type])
|
|
454
|
+
next nil if separate_invocations?(write, other_class, writes)
|
|
453
455
|
|
|
454
456
|
build_ivar_write_mismatch_diagnostic(path, write[:node], class_name, ivar_name, first_class, other_class)
|
|
455
457
|
end
|
|
456
458
|
end
|
|
457
459
|
|
|
460
|
+
# ADR-125 — a divergent write is never live together with the writes it conflicts with when the class
|
|
461
|
+
# serves one entry point per instance (a plugin-declared per-invocation class, e.g. a Rails controller:
|
|
462
|
+
# one action per request) and every conflicting write — non-nil, of a concrete class other than this
|
|
463
|
+
# write's — sits in a DIFFERENT action of the same class. An action is a public method no declared
|
|
464
|
+
# non-action macro names (`before_action :x`, `helper_method :y`); {IvarWriteCollector} decides it. A
|
|
465
|
+
# conflicting write in a private or protected helper, a callback target, a `helper_method`,
|
|
466
|
+
# `initialize`, or the same method keeps the finding. Which helper each action reaches is not proved
|
|
467
|
+
# here.
|
|
468
|
+
def separate_invocations?(write, write_class, writes)
|
|
469
|
+
return false unless write[:action]
|
|
470
|
+
|
|
471
|
+
writes.all? do |other|
|
|
472
|
+
other_class = ivar_class_for(other[:type])
|
|
473
|
+
next true if other_class.nil? || other_class == "NilClass" || other_class == write_class
|
|
474
|
+
|
|
475
|
+
other[:action] && other[:method_name] != write[:method_name]
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
458
479
|
def declaration_admits?(declared, *write_types)
|
|
459
480
|
return false if declared.nil?
|
|
460
481
|
|
|
@@ -729,7 +750,7 @@ module Rigor
|
|
|
729
750
|
# branch below because a plugin is consulted ONCE for the whole receiver, union included, so the
|
|
730
751
|
# gate belongs where that one consult is rather than duplicated into each shape's path.
|
|
731
752
|
#
|
|
732
|
-
# The union side of that placement
|
|
753
|
+
# The union side of that placement is reachable. The
|
|
733
754
|
# shape needs a non-nil union of at least two RBS-DECLARED classes — an undeclared arm is an
|
|
734
755
|
# ADR-26 open receiver and `union_arm_blocks_undefined_fire?` bails before the method lookup,
|
|
735
756
|
# which is why the earlier probes read silent and were misread as the gate being unreachable.
|
|
@@ -776,13 +797,6 @@ module Rigor
|
|
|
776
797
|
return union_undefined_method_diagnostic(path, call_node, receiver_type, scope, lexical_sites)
|
|
777
798
|
end
|
|
778
799
|
|
|
779
|
-
# ADR-26 — a plugin may declare a class "open": one
|
|
780
|
-
# known to respond beyond its RBS-declared method
|
|
781
|
-
# surface (e.g. `ActiveRecord::Relation`, which
|
|
782
|
-
# delegates an unbounded set of user-defined scopes to
|
|
783
|
-
# its model). Flagging an undefined method on a class
|
|
784
|
-
# with an open dynamic surface is unsound, so the rule
|
|
785
|
-
# skips it.
|
|
786
800
|
# An unbounded receiver surface: either a plugin-declared
|
|
787
801
|
# open receiver (ADR-26 — e.g. `ActiveRecord::Relation`), or
|
|
788
802
|
# a type Rigor synthesized (a missing-namespace module / a
|
|
@@ -818,6 +832,7 @@ module Rigor
|
|
|
818
832
|
return nil if method_def
|
|
819
833
|
|
|
820
834
|
return nil if last_resort_surface_answers?(receiver_type, class_name, call_node, scope, kind, lexical_sites)
|
|
835
|
+
return nil if open_project_singleton?(receiver_type, class_name, call_node, scope)
|
|
821
836
|
return nil if subclass_instance_may_answer?(rendered_receiver, class_name, call_node.name, scope,
|
|
822
837
|
call_node.receiver)
|
|
823
838
|
|
|
@@ -969,7 +984,8 @@ module Rigor
|
|
|
969
984
|
# ({RbsReceiverSurface}). Last for {#ancestry_declares_method?}'s reason: it walks the class graph.
|
|
970
985
|
def rbs_ancestor_may_answer?(class_name, method_name, scope, kind)
|
|
971
986
|
if kind == :singleton
|
|
972
|
-
RbsReceiverSurface.singleton_hook?(scope, class_name)
|
|
987
|
+
RbsReceiverSurface.singleton_hook?(scope, class_name) ||
|
|
988
|
+
RbsReceiverSurface.singleton_mixin_may_answer?(scope, class_name, method_name)
|
|
973
989
|
else
|
|
974
990
|
RbsReceiverSurface.ancestor_may_answer?(scope, class_name, method_name)
|
|
975
991
|
end
|
|
@@ -1225,12 +1241,22 @@ module Rigor
|
|
|
1225
1241
|
# body is morally a class body, so ADR-34 stays silent there — including on a genuinely
|
|
1226
1242
|
# undefined name inside the block. Ranges are computed once per file.
|
|
1227
1243
|
return nil if call_inside_receiver_eval_ranges?(eval_ranges, call_node)
|
|
1228
|
-
|
|
1229
|
-
return nil if refined_method_in_effect?("Object", call_node, scope, :instance, lexical_sites)
|
|
1244
|
+
return nil if lexically_declined_toplevel_call?(call_node, scope, lexical_sites, scope_index)
|
|
1230
1245
|
|
|
1231
1246
|
build_unresolved_toplevel_diagnostic(path, call_node)
|
|
1232
1247
|
end
|
|
1233
1248
|
|
|
1249
|
+
# The `call.unresolved-toplevel` declines that read the file's syntax, asked last because they walk it.
|
|
1250
|
+
def lexically_declined_toplevel_call?(call_node, scope, lexical_sites, scope_index = nil)
|
|
1251
|
+
# ADR-121 WD7 — a refinement of `Object` (or one Rigor cannot read) in effect here may define the name.
|
|
1252
|
+
return true if refined_method_in_effect?("Object", call_node, scope, :instance, lexical_sites)
|
|
1253
|
+
return false if lexical_sites.nil?
|
|
1254
|
+
|
|
1255
|
+
# Issue #1817 — a block handed to a project method that may `module_eval` / `instance_exec` it runs under
|
|
1256
|
+
# another `self`, so a call in it is not a top-level one ({RebindingBlockCallees}).
|
|
1257
|
+
RebindingBlockCallees.inside_rebound_block?(call_node, scope, lexical_sites.block_calls, scope_index)
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1234
1260
|
# Issue #1383 — RBS core declares `main`'s private singleton methods on
|
|
1235
1261
|
# `RBS::Unnamed::TopLevelSelfClass`, so the set follows RBS rather than a hand-kept
|
|
1236
1262
|
# list. CRuby 4.0's `main` also has `ruby2_keywords`, which RBS does not declare.
|
|
@@ -1310,14 +1336,14 @@ module Rigor
|
|
|
1310
1336
|
# (`METACLASS_ARMS`, "a `plugin_class : Class` really holds a `Plugin` subclass with `.manifest`");
|
|
1311
1337
|
# the scalar rule enumerated `Module`'s own RBS instead and reported them.
|
|
1312
1338
|
#
|
|
1313
|
-
# Kept beside `unbounded_receiver_surface?` rather than folded into it: that predicate has
|
|
1314
|
-
#
|
|
1339
|
+
# Kept beside `unbounded_receiver_surface?` rather than folded into it: that predicate has other
|
|
1340
|
+
# callers, and #742 had no evidence that any of them spoke on these receivers.
|
|
1315
1341
|
#
|
|
1316
1342
|
# Issue #879 supplied it for exactly one — `call.wrong-arity`, which decided `v.hexdigest(1, 2, 3)`
|
|
1317
1343
|
# against `Digest::Instance`'s declaration on the same run where `v.no_such_method_zzz` was declined
|
|
1318
1344
|
# as unenumerable. One receiver, one run, two answers about whether that surface can be read. The
|
|
1319
1345
|
# reporter could make no other caller speak: `raise` on a module- or `Class`-typed operand is silent,
|
|
1320
|
-
# and `Class`-typed `.new` with a wrong arity is silent too, so the other
|
|
1346
|
+
# and `Class`-typed `.new` with a wrong arity is silent too, so the other callers stay on
|
|
1321
1347
|
# `unbounded_receiver_surface?` until something demonstrates otherwise.
|
|
1322
1348
|
#
|
|
1323
1349
|
# Arity takes {#unenumerable_receiver_shape?}, not this whole predicate: ADR-26's open receivers are
|
|
@@ -1419,14 +1445,16 @@ module Rigor
|
|
|
1419
1445
|
end
|
|
1420
1446
|
|
|
1421
1447
|
# ADR-67 WD6b — the argument-position analogue: true when `arg` is rooted at an inferred-parameter
|
|
1422
|
-
# local. The argument-type-mismatch rule declines on it for the same lower-bound reason.
|
|
1448
|
+
# local. The argument-type-mismatch rule declines on it for the same lower-bound reason. `scope` is the
|
|
1449
|
+
# call's, and the receiver and any earlier argument run before `arg` and may rebind what a conditional's
|
|
1450
|
+
# predicate in it reads (`m((c = false; 1), c ? param : "x")`), so a predicate there is never folded.
|
|
1423
1451
|
def inferred_param_argument?(arg, scope)
|
|
1424
|
-
inferred_param_rooted?(arg, scope)
|
|
1452
|
+
inferred_param_rooted?(arg, scope, fold: false)
|
|
1425
1453
|
end
|
|
1426
1454
|
|
|
1427
1455
|
# ADR-67 WD6b — see {CheckRules::InferredParamGuard} for the shared root-walk contract.
|
|
1428
|
-
def inferred_param_rooted?(node, scope)
|
|
1429
|
-
InferredParamGuard.rooted?(node, scope)
|
|
1456
|
+
def inferred_param_rooted?(node, scope, fold: true)
|
|
1457
|
+
InferredParamGuard.rooted?(node, scope, fold: fold)
|
|
1430
1458
|
end
|
|
1431
1459
|
|
|
1432
1460
|
def constant_class_name(value)
|
|
@@ -1487,12 +1515,118 @@ module Rigor
|
|
|
1487
1515
|
# overlay open receiver ({GEM_OVERLAY_OPEN_RECEIVERS}) whose
|
|
1488
1516
|
# bundled directory this run actually loaded, a core-overlay
|
|
1489
1517
|
# open receiver ({CORE_OVERLAY_OPEN_RECEIVERS}), or a
|
|
1490
|
-
# Rigor-synthesized stub type (see `RbsLoader#synthesized_type_names`)
|
|
1518
|
+
# Rigor-synthesized stub type (see `RbsLoader#synthesized_type_names`),
|
|
1519
|
+
# or a project-only reopen of a Ruby library class whose own RBS
|
|
1520
|
+
# did not load ({#ruby_library_reopen?}).
|
|
1491
1521
|
def unbounded_receiver_surface?(class_name, scope)
|
|
1492
1522
|
bare_name = class_name.to_s.sub(/\A::/, "")
|
|
1493
1523
|
open_receiver?(class_name, scope) || synthesized_stub_receiver?(class_name, scope) ||
|
|
1494
1524
|
CORE_OVERLAY_OPEN_RECEIVERS.include?(bare_name) ||
|
|
1495
|
-
(GEM_OVERLAY_OPEN_RECEIVERS.include?(bare_name) && gem_overlay_loaded?(scope))
|
|
1525
|
+
(GEM_OVERLAY_OPEN_RECEIVERS.include?(bare_name) && gem_overlay_loaded?(scope)) ||
|
|
1526
|
+
ruby_library_reopen?(class_name, scope)
|
|
1527
|
+
end
|
|
1528
|
+
|
|
1529
|
+
# Issue #2438 — a class-method call on a class the project's own signatures name but whose source leaves
|
|
1530
|
+
# the singleton surface open. Before `rigor sig-gen --write` such a class was source-only and an
|
|
1531
|
+
# unresolved call on it was silent; the sidecar must not turn the same call into a finding. Two shapes:
|
|
1532
|
+
#
|
|
1533
|
+
# - `C.instance` where `C`, or a project class up its superclass chain, `include`s the stdlib `Singleton`:
|
|
1534
|
+
# the answer is `SingletonMixinDispatch.answers?`, the predicate the typer itself uses, so the rule never
|
|
1535
|
+
# reports a call the engine typed. The class's other members are irrelevant: the mixin's hook defines
|
|
1536
|
+
# `instance`.
|
|
1537
|
+
# - every project declaration of `C` is member-less (`RbsLoader#memberless_project_class?`) AND the source
|
|
1538
|
+
# leaves `C` open ({#open_source_class?}). A member-less declaration does not describe the class's
|
|
1539
|
+
# surface, but a class whose whole definition Rigor can see in `paths:` is still closed, so a typo on it
|
|
1540
|
+
# keeps firing. Instance-side calls keep being checked; declaring any member re-closes the class.
|
|
1541
|
+
def open_project_singleton?(receiver_type, class_name, call_node, scope)
|
|
1542
|
+
return false unless receiver_type.is_a?(Type::Singleton)
|
|
1543
|
+
return true if Inference::MethodDispatcher::SingletonMixinDispatch.answers?(
|
|
1544
|
+
scope, receiver_type, call_node.name, call_node.arguments.nil?
|
|
1545
|
+
)
|
|
1546
|
+
|
|
1547
|
+
loader = scope.environment&.rbs_loader
|
|
1548
|
+
return false if loader.nil? || !loader.respond_to?(:memberless_project_class?)
|
|
1549
|
+
|
|
1550
|
+
loader.memberless_project_class?(class_name) && open_source_class?(class_name, scope, loader)
|
|
1551
|
+
end
|
|
1552
|
+
|
|
1553
|
+
OPENING_HOOKS = %i[inherited included extended prepended method_missing respond_to_missing?].freeze
|
|
1554
|
+
private_constant :OPENING_HOOKS
|
|
1555
|
+
|
|
1556
|
+
CLOSED_ROOTS = Set["Object", "BasicObject"].freeze
|
|
1557
|
+
private_constant :CLOSED_ROOTS
|
|
1558
|
+
|
|
1559
|
+
# Issue #2438 — whether a class's singleton surface may hold methods its source in `paths:` does not show,
|
|
1560
|
+
# read off `Scope::ResolutionChain` (`:constants` flavor, so a project module that holds only constants is
|
|
1561
|
+
# still a project entry). Open when the class, or a project class up its superclass chain:
|
|
1562
|
+
#
|
|
1563
|
+
# 1. `extend`s a module (any singleton-side entry beyond the classes' own singletons), or records an
|
|
1564
|
+
# `inherited` / `included` / `extended` / `prepended` hook or `method_missing` / `respond_to_missing?`.
|
|
1565
|
+
# A plain `include` or `prepend` adds instance methods only, so it leaves the singleton side closed;
|
|
1566
|
+
# 2. has no `class` / `module` body of its own inside `paths:` (`class Ext::Foo` names `Ext` without one);
|
|
1567
|
+
# 3. or the chain reaches a class the project does not define, other than `Object` / `BasicObject`: an
|
|
1568
|
+
# external superclass (`ApplicationRecord < ActiveRecord::Base`, `MyError < StandardError`), a
|
|
1569
|
+
# superclass written as an expression, or — where the source writes no superclass for the last class —
|
|
1570
|
+
# the superclass its member-less declaration writes, unless that is a project class.
|
|
1571
|
+
#
|
|
1572
|
+
# A definition or reopening in a file outside `paths:` cannot be seen, so a class whose body is inside
|
|
1573
|
+
# `paths:` and whose methods live only outside it counts as closed.
|
|
1574
|
+
def open_source_class?(class_name, scope, loader)
|
|
1575
|
+
name = class_name.to_s.delete_prefix("::")
|
|
1576
|
+
instance = Scope::ResolutionChain.for(scope, name, :instance, :constants)
|
|
1577
|
+
singleton = Scope::ResolutionChain.for(scope, name, :singleton, :constants)
|
|
1578
|
+
[instance, singleton].each { |chain| chain.record(scope) }
|
|
1579
|
+
classes = instance.level_classes.compact
|
|
1580
|
+
return true if classes.empty? || instance.truncated?
|
|
1581
|
+
|
|
1582
|
+
sources = scope.discovered_class_sources
|
|
1583
|
+
return true if classes.any? { |klass| (sources[klass] || []).empty? || opening_hook?(klass, scope) }
|
|
1584
|
+
return true if opening_entry?(instance, classes, mixins: false) ||
|
|
1585
|
+
opening_entry?(singleton, classes, mixins: true)
|
|
1586
|
+
|
|
1587
|
+
open_tail?(instance, classes.last, scope, loader)
|
|
1588
|
+
end
|
|
1589
|
+
|
|
1590
|
+
# An external superclass other than `Object` / `BasicObject` opens either side; a module entry opens the
|
|
1591
|
+
# chain only where `mixins` is set (the singleton side, where it is an `extend`).
|
|
1592
|
+
def opening_entry?(chain, classes, mixins:)
|
|
1593
|
+
chain.entries.any? do |entry|
|
|
1594
|
+
if entry.external? && entry.superclass_edge
|
|
1595
|
+
!CLOSED_ROOTS.include?(entry.raw.to_s.delete_prefix("::"))
|
|
1596
|
+
else
|
|
1597
|
+
mixins && !classes.include?(entry.name)
|
|
1598
|
+
end
|
|
1599
|
+
end
|
|
1600
|
+
end
|
|
1601
|
+
|
|
1602
|
+
def opening_hook?(klass, scope)
|
|
1603
|
+
OPENING_HOOKS.any? do |hook|
|
|
1604
|
+
scope.discovered_method?(klass, hook, :singleton) || scope.discovered_method?(klass, hook, :instance)
|
|
1605
|
+
end
|
|
1606
|
+
end
|
|
1607
|
+
|
|
1608
|
+
# The last class's own superclass, which the chain does not name: an expression the tables cannot resolve,
|
|
1609
|
+
# or one only the member-less declaration writes (the chain ends at the implicit `Object` exactly when
|
|
1610
|
+
# the source writes none).
|
|
1611
|
+
def open_tail?(chain, last, scope, loader)
|
|
1612
|
+
return true if chain.opaque_tail?(scope)
|
|
1613
|
+
|
|
1614
|
+
raw = loader.memberless_project_superclass(last)
|
|
1615
|
+
return false if raw.nil? || CLOSED_ROOTS.include?(raw.delete_prefix("::"))
|
|
1616
|
+
|
|
1617
|
+
!scope.discovered_classes.key?(raw.delete_prefix("::"))
|
|
1618
|
+
end
|
|
1619
|
+
|
|
1620
|
+
# Issue #2174 — `class Matrix` reopened with one `#:`-annotated method, and no `matrix` signatures
|
|
1621
|
+
# loaded, gives Matrix a declaration holding only that method ({RbsLoader#ruby_library_reopen?}).
|
|
1622
|
+
# `Matrix.identity(2)` is correct Ruby, so the class's surface is treated as open: `call.undefined-method`
|
|
1623
|
+
# declines on it, and a signature it only inherits (`Object#initialize` for `Matrix.new(rows)`) stops
|
|
1624
|
+
# deciding arity and argument types. Methods the project's own declaration lists stay checked.
|
|
1625
|
+
def ruby_library_reopen?(class_name, scope)
|
|
1626
|
+
loader = scope.environment&.rbs_loader
|
|
1627
|
+
return false if loader.nil? || !loader.respond_to?(:ruby_library_reopen?)
|
|
1628
|
+
|
|
1629
|
+
loader.ruby_library_reopen?(class_name)
|
|
1496
1630
|
end
|
|
1497
1631
|
|
|
1498
1632
|
def open_receiver?(class_name, scope)
|
|
@@ -1798,16 +1932,15 @@ module Rigor
|
|
|
1798
1932
|
# Slice 7 phase 11 — wrong-arity diagnostic. Fires when
|
|
1799
1933
|
# an explicit-receiver `Prism::CallNode` resolves to a
|
|
1800
1934
|
# method whose declared overloads do not admit the
|
|
1801
|
-
# supplied positional argument count
|
|
1802
|
-
#
|
|
1803
|
-
#
|
|
1804
|
-
#
|
|
1805
|
-
# arguments
|
|
1806
|
-
#
|
|
1807
|
-
#
|
|
1808
|
-
#
|
|
1809
|
-
#
|
|
1810
|
-
# disqualifies the rule.
|
|
1935
|
+
# supplied positional argument count (across all
|
|
1936
|
+
# overloads, see {#compute_arity_envelope}). One overload
|
|
1937
|
+
# with required keyword arguments or trailing positionals
|
|
1938
|
+
# leaves the whole method unchecked; calls with `*splat`, keyword, or
|
|
1939
|
+
# block-pass arguments are silently skipped to avoid
|
|
1940
|
+
# false positives. The check piggybacks on the same
|
|
1941
|
+
# scope-index lookup used by `undefined_method_diagnostic`;
|
|
1942
|
+
# it returns nil when the call's receiver / RBS coverage /
|
|
1943
|
+
# call shape disqualifies the rule.
|
|
1811
1944
|
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1812
1945
|
def wrong_arity_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
1813
1946
|
return nil if call_node.receiver.nil?
|
|
@@ -2780,9 +2913,37 @@ module Rigor
|
|
|
2780
2913
|
|
|
2781
2914
|
# Whether any project or `pre_eval:` file aliases `name`, on either side, which can make it another variable.
|
|
2782
2915
|
def global_aliased?(scope, name)
|
|
2916
|
+
record_global_census(Inference::GlobalWriteCensus.alias_entry(name))
|
|
2783
2917
|
Inference::GlobalWriteCensus.aliased?(global_write_census(scope), name)
|
|
2784
2918
|
end
|
|
2785
2919
|
|
|
2920
|
+
# Issue #2158 — a read of the whole-program census records the `global_census:` key of each entry it asks
|
|
2921
|
+
# about ({Inference::GlobalWriteCensus.dependency_key}): a file that starts or stops writing the entry (a `def
|
|
2922
|
+
# write`, an `alias $stdout $x`, a computed `define_method`) re-checks the write. A `pre_eval:` file moves the
|
|
2923
|
+
# snapshot's own fingerprint.
|
|
2924
|
+
def record_global_census(*entries)
|
|
2925
|
+
return unless DependencyRecorder.active?
|
|
2926
|
+
|
|
2927
|
+
entries.each do |entry|
|
|
2928
|
+
DependencyRecorder.read_name(:global_census, Inference::GlobalWriteCensus.dependency_key(entry))
|
|
2929
|
+
end
|
|
2930
|
+
end
|
|
2931
|
+
|
|
2932
|
+
# Whether the census holds any of `entries`, recording only what can flip the answer: the entries present
|
|
2933
|
+
# when it holds one (the answer stays true until every one of them vanishes, and another appearing changes
|
|
2934
|
+
# nothing), every entry when it holds none.
|
|
2935
|
+
#
|
|
2936
|
+
# This rests on an invariant: every census entry can only move a write toward being silenced, and a reported
|
|
2937
|
+
# write is always re-checked through `IncrementalSession#global_write_reporters`, so recording only the
|
|
2938
|
+
# present entries on a yes is sound. A future census entry that makes a write reported would break it, and
|
|
2939
|
+
# its reader would have to record every entry. Recording every entry on a no is defence in depth, since the
|
|
2940
|
+
# write that answer leaves reported re-checks through `global_write_reporters` anyway.
|
|
2941
|
+
def census_holds_any?(census, entries)
|
|
2942
|
+
present = entries.select { |entry| census.include?(entry) }
|
|
2943
|
+
record_global_census(*(present.empty? ? entries : present))
|
|
2944
|
+
!present.empty?
|
|
2945
|
+
end
|
|
2946
|
+
|
|
2786
2947
|
# A literal is rejected when its class is none the setter takes and, for a setter that also converts
|
|
2787
2948
|
# (`to_str`, `to_int`) or asks for `write`, the literal's object cannot answer that method.
|
|
2788
2949
|
def literal_rejected?(class_name, contract, node, scope, lexical_sites)
|
|
@@ -2830,11 +2991,15 @@ module Rigor
|
|
|
2830
2991
|
end
|
|
2831
2992
|
|
|
2832
2993
|
def program_may_answer?(ancestors, names, census, scope)
|
|
2833
|
-
|
|
2994
|
+
defines = [Inference::GlobalWriteCensus::DEFINES_ANY, *names.map { |name| [:defines, name] }]
|
|
2995
|
+
return true if census_holds_any?(census, defines)
|
|
2834
2996
|
|
|
2835
2997
|
dynamic_mark = Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK
|
|
2836
2998
|
return true if ancestors.any? { |owner| scope.parameter_envelopes_of(owner).key?(dynamic_mark) }
|
|
2837
2999
|
|
|
3000
|
+
# One key for every top-level mixin, a deliberate trade-off: the reader asks about each, so a mixin
|
|
3001
|
+
# appearing or vanishing anywhere re-checks every write that reached this step.
|
|
3002
|
+
record_global_census(%i[main_mixin *])
|
|
2838
3003
|
main_mixins = Inference::GlobalWriteCensus.main_mixins(census)
|
|
2839
3004
|
main_mixins.any? { |mixin| rbs_mixin_may_answer?(mixin, names, scope) } ||
|
|
2840
3005
|
mixin_may_answer?(ancestors, names, scope)
|
|
@@ -2876,7 +3041,7 @@ module Rigor
|
|
|
2876
3041
|
# active refinement on Ruby 4.0.5; a refined `respond_to_missing?` or `respond_to?`, and every implicit
|
|
2877
3042
|
# conversion, do not, so a refinement that adds only those leaves the write reported.
|
|
2878
3043
|
def refined_writer_in_effect?(census, node, scope, lexical_sites)
|
|
2879
|
-
Inference::GlobalWriteCensus
|
|
3044
|
+
census_holds_any?(census, [Inference::GlobalWriteCensus::REFINES_ANY, %i[refines write]]) &&
|
|
2880
3045
|
(lexical_sites.nil? || !scope.declared_refinements.empty? ||
|
|
2881
3046
|
lexical_sites.refinements.any_at?(node.location.start_offset))
|
|
2882
3047
|
end
|
|
@@ -2911,8 +3076,7 @@ module Rigor
|
|
|
2911
3076
|
# - Elsif chains (`subsequent` is itself an `IfNode`) ARE flagged — the entire downstream chain is
|
|
2912
3077
|
# unreachable when the outer predicate is a constant literal.
|
|
2913
3078
|
#
|
|
2914
|
-
#
|
|
2915
|
-
# mutation gaps named above are closed.
|
|
3079
|
+
# Inferred-constant predicates are the `flow.always-truthy-condition` rule's ({AlwaysTruthyConditionCollector}).
|
|
2916
3080
|
def unreachable_branch_diagnostic(path, node, scope_index)
|
|
2917
3081
|
scope = scope_index[node]
|
|
2918
3082
|
return nil if scope.nil?
|
|
@@ -3023,7 +3187,7 @@ module Rigor
|
|
|
3023
3187
|
build_visibility_mismatch_diagnostic(path, call_node, "singleton(#{class_name})")
|
|
3024
3188
|
end
|
|
3025
3189
|
|
|
3026
|
-
PRIVATE_NAMES_MEMO = :rigor_check_rules_private_singleton_names
|
|
3190
|
+
PRIVATE_NAMES_MEMO = RunScopedSlots.register(:rigor_check_rules_private_singleton_names)
|
|
3027
3191
|
private_constant :PRIVATE_NAMES_MEMO
|
|
3028
3192
|
|
|
3029
3193
|
# Issue #1984 — the table-only gate the singleton side asks before it reads a chain, as the instance side asks
|
|
@@ -3032,6 +3196,7 @@ module Rigor
|
|
|
3032
3196
|
# `Widget.new` would read, and file the dependency edges of, the whole singleton chain. The set is memoised per
|
|
3033
3197
|
# discovery index.
|
|
3034
3198
|
def private_singleton_candidate?(scope, name)
|
|
3199
|
+
DependencyRecorder.read_visibility("*", name) if DependencyRecorder.active?
|
|
3035
3200
|
discovery = scope.discovery
|
|
3036
3201
|
slot = Thread.current[PRIVATE_NAMES_MEMO]
|
|
3037
3202
|
unless slot && slot[0].equal?(discovery)
|
|
@@ -3058,7 +3223,8 @@ module Rigor
|
|
|
3058
3223
|
return true if rows.nil?
|
|
3059
3224
|
|
|
3060
3225
|
offset = call_node.location.start_offset
|
|
3061
|
-
|
|
3226
|
+
# A row with no side (a `def` in a block, a `define_method` name) is a block's range or no range at all.
|
|
3227
|
+
rows.none? { |(start, finish, _name, kind, _owner)| !kind.nil? && start <= offset && offset < finish }
|
|
3062
3228
|
end
|
|
3063
3229
|
|
|
3064
3230
|
SINGLETON_WILDCARD_SLOT = %i[singleton *].freeze
|
|
@@ -3070,6 +3236,7 @@ module Rigor
|
|
|
3070
3236
|
discovery = scope.discovery
|
|
3071
3237
|
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :singleton, :methods)
|
|
3072
3238
|
chain.entries.any? do |entry|
|
|
3239
|
+
DependencyRecorder.read_visibility(entry.name, SINGLETON_WILDCARD_SLOT.last) unless entry.external?
|
|
3073
3240
|
!entry.external? && discovery.contested?(:discovered_method_visibilities,
|
|
3074
3241
|
[entry.name, SINGLETON_WILDCARD_SLOT])
|
|
3075
3242
|
end
|
|
@@ -3299,12 +3466,9 @@ module Rigor
|
|
|
3299
3466
|
# - Receiver must be Nominal / Singleton / Constant
|
|
3300
3467
|
# (the same `concrete_class_name` test).
|
|
3301
3468
|
# - Method must be in RBS.
|
|
3302
|
-
# -
|
|
3303
|
-
#
|
|
3304
|
-
#
|
|
3305
|
-
# overload requires the dispatcher's full
|
|
3306
|
-
# acceptance plumbing.
|
|
3307
|
-
# - The selected overload must have NO
|
|
3469
|
+
# - Overloads: a single method type is checked exactly; with several, an argument is a mismatch only
|
|
3470
|
+
# when every overload rejects it ({#multi_overload_argument_mismatch}).
|
|
3471
|
+
# - The overload checked must have NO
|
|
3308
3472
|
# rest_positionals and NO trailing positionals —
|
|
3309
3473
|
# the two shapes under which argument N no longer
|
|
3310
3474
|
# binds to positional parameter N. Keyword
|
|
@@ -4014,16 +4178,14 @@ module Rigor
|
|
|
4014
4178
|
# OWN declared return, and the sibling override rules gate on `defined_on?` — this one did not.
|
|
4015
4179
|
# The inherited-declaration signal belongs to the ADR-35 family, which requires both sides
|
|
4016
4180
|
# authored. Decided in ADR-110 WD3.
|
|
4017
|
-
# - Skips methods whose enclosing
|
|
4018
|
-
# (top-level / module-level methods land outside the rule).
|
|
4181
|
+
# - Skips methods whose enclosing `self` names no class (top-level methods land outside the rule).
|
|
4019
4182
|
# - Skips methods whose body's last expression is absent or types as `Dynamic[top]` (the
|
|
4020
4183
|
# analyzer's fail-soft fallback) — emitting on `Dynamic[top]` would be noise.
|
|
4021
4184
|
# - Compares the inferred body type against the declared return via `accepts?`:
|
|
4022
4185
|
# :yes → silent
|
|
4023
4186
|
# :no → emit at :error (severity_profile may re-stamp; default `balanced` keeps the
|
|
4024
4187
|
# authored severity).
|
|
4025
|
-
# :maybe →
|
|
4026
|
-
# "Severity profile".
|
|
4188
|
+
# :maybe → silent (see {#compare_return}; ADR-8's `strict` promotion is deferred).
|
|
4027
4189
|
def return_type_mismatch_diagnostic(path, def_node, scope_index)
|
|
4028
4190
|
return nil if def_node.body.nil?
|
|
4029
4191
|
|
|
@@ -4159,9 +4321,9 @@ module Rigor
|
|
|
4159
4321
|
# precision improvements.
|
|
4160
4322
|
def compare_return(declared, inferred)
|
|
4161
4323
|
result = declared.accepts(inferred)
|
|
4162
|
-
return
|
|
4324
|
+
return nil unless result.no?
|
|
4163
4325
|
|
|
4164
|
-
|
|
4326
|
+
:error
|
|
4165
4327
|
end
|
|
4166
4328
|
|
|
4167
4329
|
def build_return_type_mismatch_diagnostic(path, def_node, declared, inferred, severity)
|
|
@@ -4202,6 +4364,9 @@ module Rigor
|
|
|
4202
4364
|
|
|
4203
4365
|
override_visibility = scope.discovered_method_visibility(class_name, method_name)
|
|
4204
4366
|
return nil if override_visibility.nil?
|
|
4367
|
+
# Issue #2013 — a contested slot (a later `define_method` or alias gave the name another visibility, or a
|
|
4368
|
+
# write the walk cannot place) proves no reduction.
|
|
4369
|
+
return nil if scope.discovery.contested?(:discovered_method_visibilities, [class_name, method_name])
|
|
4205
4370
|
|
|
4206
4371
|
parent = nearest_ancestor_visibility(scope, class_name, method_name)
|
|
4207
4372
|
return nil if parent.nil?
|
|
@@ -149,8 +149,8 @@ module Rigor
|
|
|
149
149
|
|
|
150
150
|
# True when `diagnostic` reports a failure inside Rigor that left the run readable but invalid as a
|
|
151
151
|
# measurement of Rigor — see {ANALYZER_DEFECT_RULES}. Orthogonal to {.discards_file_analysis?}: the
|
|
152
|
-
# user-facing tier reads the run; the Rigor-measuring tier should refuse it (the mutation fuzz
|
|
153
|
-
#
|
|
152
|
+
# user-facing tier reads the run; the Rigor-measuring tier should refuse it (the mutation fuzz and the
|
|
153
|
+
# ADR-69 kill oracle both do, #790).
|
|
154
154
|
def analyzer_defect?(diagnostic)
|
|
155
155
|
ANALYZER_DEFECT_RULES.include?(diagnostic.rule)
|
|
156
156
|
end
|
|
@@ -74,8 +74,8 @@ module Rigor
|
|
|
74
74
|
@sources = Set.new
|
|
75
75
|
@missing = Set.new
|
|
76
76
|
# ADR-46 slice 4 — symbol-granularity tracking.
|
|
77
|
-
# `symbol_sources`: source_path →
|
|
78
|
-
# `ancestry_sources`:
|
|
77
|
+
# `symbol_sources`: source_path → the "ClassName#method" names read from it, for method-call deps.
|
|
78
|
+
# `ancestry_sources`: the source paths read for class-ancestry (superclass / include) deps —
|
|
79
79
|
# file-granularity by nature (a superclass edge touches the whole class).
|
|
80
80
|
@symbol_sources = Hash.new { |h, k| h[k] = Set.new }
|
|
81
81
|
@ancestry_sources = Set.new
|
|
@@ -103,8 +103,8 @@ module Rigor
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
# Frozen record of one file's cross-file reads.
|
|
106
|
-
# `symbol_sources`: source_path → frozen
|
|
107
|
-
# `ancestry_sources`: frozen
|
|
106
|
+
# `symbol_sources`: source_path → frozen set of "ClassName#method" names (method-call edges).
|
|
107
|
+
# `ancestry_sources`: frozen set of source paths (class-ancestry edges, file-granularity).
|
|
108
108
|
Record = Data.define(:consumer, :sources, :missing, :symbol_sources, :ancestry_sources)
|
|
109
109
|
|
|
110
110
|
# ADR-84 WD2 — a replayable, consumer-independent read-set: `reads` is a frozen Set of frozen
|
|
@@ -406,6 +406,17 @@ module Rigor
|
|
|
406
406
|
end
|
|
407
407
|
end
|
|
408
408
|
|
|
409
|
+
# Issue #2134 — {read_name} for a read of `class_name`'s visibility slots for `method_name`, on either side, or
|
|
410
|
+
# of their contest: `visibility:<class>#<method>`. `IncrementalSession#visibility_name_affected` fires the key
|
|
411
|
+
# when a file's visibility entry or contest for the slot moves. A reader that asks whether ANY class records
|
|
412
|
+
# the name private passes `"*"` as the class; the key fires when any entry of the name moves. A wildcard
|
|
413
|
+
# slot (`[:singleton, :*]`) is `*` as the method name.
|
|
414
|
+
def read_visibility(class_name, method_name)
|
|
415
|
+
return if Thread.current[KEY].nil?
|
|
416
|
+
|
|
417
|
+
read_name(:visibility, "#{class_name}##{method_name}")
|
|
418
|
+
end
|
|
419
|
+
|
|
409
420
|
# The miss-only spelling of {read_name}: a cross-file lookup of `name` (kind `:method` / `:class` /
|
|
410
421
|
# `:toplevel` / …) that resolved to nothing. Kept as the name the internal spec's negative-edge
|
|
411
422
|
# contract uses.
|
|
@@ -25,9 +25,9 @@ module Rigor
|
|
|
25
25
|
@mutex = Mutex.new
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
# @return frozen snapshot of the recorded boundary-cross events. Each entry is a Data
|
|
29
|
-
#
|
|
30
|
-
#
|
|
28
|
+
# @return frozen snapshot of the recorded boundary-cross events. Each entry is a Data with `class_name`,
|
|
29
|
+
# `method_name`, `gem_name`, and `rbs_display` (the authoritative-side type's human-facing form, embedded
|
|
30
|
+
# into the diagnostic message).
|
|
31
31
|
def entries
|
|
32
32
|
@mutex.synchronize { @entries.dup.freeze }
|
|
33
33
|
end
|
|
@@ -50,6 +50,17 @@ module Rigor
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# The pool drain ({Rigor::Analysis::WorkerSession#drain_reporters}): the frozen entries.
|
|
54
|
+
def snapshot
|
|
55
|
+
entries
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Replays one worker's {#snapshot}; the record-time dedup collapses every worker's copy. Nil records
|
|
59
|
+
# nothing.
|
|
60
|
+
def absorb(snapshot)
|
|
61
|
+
Array(snapshot).each { |entry| record(**entry.to_h) }
|
|
62
|
+
end
|
|
63
|
+
|
|
53
64
|
private
|
|
54
65
|
|
|
55
66
|
def same_key?(existing, entry)
|