rigortype 0.4.6 → 0.4.8
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 +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- 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 +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- 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 +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- 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/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- 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 +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -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 +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
- 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 +29 -10
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +92 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- 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 +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -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/hkt_registry_holder.rb +6 -0
- 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_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -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 +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +81 -5
- 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/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- 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 +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- 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 +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- 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/singleton_type_params.rb +104 -0
- 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 +123 -25
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.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/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +287 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- 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 +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- 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 +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- 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 +48 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- 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 +81 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +32 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- 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 +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- 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/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +238 -37
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- 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 +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- 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/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- 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 +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- 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 +11 -14
- 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 +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
- 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-graphql/lib/rigor/plugin/graphql.rb +12 -2
- 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 +8 -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 +200 -4
- 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 +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- 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/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +82 -3
|
@@ -12,11 +12,13 @@ require_relative "../type"
|
|
|
12
12
|
require_relative "diagnostic"
|
|
13
13
|
require_relative "dependency_recorder"
|
|
14
14
|
require_relative "check_rules/rule_ids"
|
|
15
|
+
require_relative "check_rules/call_facts"
|
|
15
16
|
require_relative "check_rules/inferred_param_guard"
|
|
16
17
|
require_relative "check_rules/declaration_sourced_guard"
|
|
17
18
|
require_relative "check_rules/published_constant_guard"
|
|
18
19
|
require_relative "check_rules/dead_version_guard_arms"
|
|
19
20
|
require_relative "check_rules/rule_walk"
|
|
21
|
+
require_relative "check_rules/always_raises_shield"
|
|
20
22
|
require_relative "check_rules/always_truthy_condition_collector"
|
|
21
23
|
require_relative "check_rules/unreachable_clause_collector"
|
|
22
24
|
require_relative "check_rules/shadowed_rescue_collector"
|
|
@@ -33,6 +35,8 @@ require_relative "check_rules/rebinding_block_callees"
|
|
|
33
35
|
require_relative "check_rules/rbs_receiver_surface"
|
|
34
36
|
require_relative "check_rules/special_global_setters"
|
|
35
37
|
require_relative "check_rules/ruby_deprecations"
|
|
38
|
+
require_relative "check_rules/plugin_api_surface"
|
|
39
|
+
require_relative "../typing_state"
|
|
36
40
|
|
|
37
41
|
module Rigor
|
|
38
42
|
module Analysis
|
|
@@ -53,7 +57,7 @@ module Rigor
|
|
|
53
57
|
#
|
|
54
58
|
# - implicit-self calls (no `node.receiver`) — too noisy
|
|
55
59
|
# without per-method RBS for every helper in the class.
|
|
56
|
-
# - dynamic / unknown receivers (`Dynamic[T]`, `Top
|
|
60
|
+
# - dynamic / unknown receivers (`Dynamic[T]`, `Top`)
|
|
57
61
|
# — by definition we cannot enumerate the method set.
|
|
58
62
|
# - shape carriers: `Tuple` → "Array", `HashShape` → "Hash",
|
|
59
63
|
# `Constant` → the constant's class — `concrete_class_name`
|
|
@@ -86,9 +90,8 @@ module Rigor
|
|
|
86
90
|
|
|
87
91
|
module_function
|
|
88
92
|
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
# have already produced `scope_index` through
|
|
93
|
+
# Returns the diagnostics for `root`'s subtree across the rule
|
|
94
|
+
# catalogue. The caller MUST have already produced `scope_index` through
|
|
92
95
|
# `Rigor::Inference::ScopeIndexer.index(root, default_scope:)`.
|
|
93
96
|
#
|
|
94
97
|
# @param path — used to populate
|
|
@@ -110,7 +113,8 @@ module Rigor
|
|
|
110
113
|
diagnostics = collectors[:main_pass].results.dup
|
|
111
114
|
diagnostics.concat(self_undefined_method_diagnostics(path, self_call_misses, root, scope_index))
|
|
112
115
|
COLLECTOR_DIAGNOSTIC_BUILDERS.each do |role, builder|
|
|
113
|
-
|
|
116
|
+
collector = OPTIONAL_COLLECTOR_ROLES.include?(role) ? collectors[role] : collectors.fetch(role)
|
|
117
|
+
diagnostics.concat(send(builder, path, collector.results)) if collector
|
|
114
118
|
end
|
|
115
119
|
# ADR-47 WD5 — the dead arm of a decidable version guard (#627) reports nothing: it cannot run on
|
|
116
120
|
# the Ruby being checked with, so a diagnostic there is a false positive. Applied before
|
|
@@ -141,18 +145,25 @@ module Rigor
|
|
|
141
145
|
ivar_writes: :ivar_write_mismatch_diagnostics,
|
|
142
146
|
dead_assignments: :dead_assignment_diagnostics,
|
|
143
147
|
duplicate_hash_keys: :duplicate_hash_key_diagnostics,
|
|
144
|
-
return_in_ensure: :return_in_ensure_diagnostics
|
|
148
|
+
return_in_ensure: :return_in_ensure_diagnostics,
|
|
149
|
+
plugin_api_surface: :plugin_api_surface_diagnostics
|
|
145
150
|
}.freeze
|
|
146
151
|
private_constant :COLLECTOR_DIAGNOSTIC_BUILDERS
|
|
147
152
|
|
|
153
|
+
# The roles {.build_node_collectors} builds only on request: `plugin_api_surface`, when the rule is active
|
|
154
|
+
# ({PluginApiSurface.active?}). {.diagnose} skips an absent optional role; every other role is required, and a
|
|
155
|
+
# missing one raises.
|
|
156
|
+
OPTIONAL_COLLECTOR_ROLES = %i[plugin_api_surface].freeze
|
|
157
|
+
|
|
148
158
|
# The verbatim per-node dispatch of the former inline main pass
|
|
149
159
|
# (`diagnose`'s `Source::NodeWalker.each` `case`), now invoked by
|
|
150
160
|
# {MainPassCollector} on the shared {RuleWalk}. Returns the
|
|
151
161
|
# diagnostics for one node, in the same emission order as before.
|
|
152
|
-
def main_pass_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil
|
|
162
|
+
def main_pass_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil,
|
|
163
|
+
raise_shield = nil)
|
|
153
164
|
case node
|
|
154
165
|
when Prism::CallNode
|
|
155
|
-
call_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites)
|
|
166
|
+
call_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites, raise_shield)
|
|
156
167
|
when Prism::DefNode
|
|
157
168
|
[
|
|
158
169
|
refinement_aware_return_type_mismatch(path, node, scope_index, lexical_sites),
|
|
@@ -174,7 +185,8 @@ module Rigor
|
|
|
174
185
|
# check in the body stands. The refine-body test runs only once a mismatch would report, so a file pays the
|
|
175
186
|
# {LexicalMethodSites} walk only then.
|
|
176
187
|
def refinement_aware_return_type_mismatch(path, node, scope_index, lexical_sites)
|
|
177
|
-
|
|
188
|
+
replaced = lexical_sites&.replaced_singleton_defs(scope_index)
|
|
189
|
+
diagnostic = return_type_mismatch_diagnostic(path, node, scope_index, replaced)
|
|
178
190
|
return diagnostic if diagnostic.nil? || lexical_sites.nil?
|
|
179
191
|
|
|
180
192
|
lexical_sites.refinements.refinement_def?(node) ? nil : diagnostic
|
|
@@ -187,11 +199,17 @@ module Rigor
|
|
|
187
199
|
# traversal, and hand the populated set back to {.diagnose} as
|
|
188
200
|
# `node_collectors:`. The main pass needs `path` because its per-node
|
|
189
201
|
# diagnostics carry it (ADR-53 B3c hosts it on the same walk).
|
|
190
|
-
|
|
202
|
+
#
|
|
203
|
+
# `plugin_api_surface:` adds the `plugin.api-surface` collector, which the runner asks for only when the rule
|
|
204
|
+
# resolves to a reporting severity ({PluginApiSurface.active?}).
|
|
205
|
+
def build_node_collectors(path, scope_index, root = nil, plugin_api_surface: false)
|
|
191
206
|
eval_ranges = receiver_eval_block_ranges(root)
|
|
192
207
|
lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
raise_shield = root && AlwaysRaisesShield.new(root)
|
|
209
|
+
main_pass = lambda do |node|
|
|
210
|
+
main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites, raise_shield)
|
|
211
|
+
end
|
|
212
|
+
collectors = {
|
|
195
213
|
main_pass: MainPassCollector.new(main_pass),
|
|
196
214
|
void_value_use: VoidValueUseCollector.new(scope_index),
|
|
197
215
|
always_truthy: AlwaysTruthyConditionCollector.new(scope_index),
|
|
@@ -202,6 +220,8 @@ module Rigor
|
|
|
202
220
|
duplicate_hash_keys: DuplicateHashKeyCollector.new(scope_index),
|
|
203
221
|
return_in_ensure: ReturnInEnsureCollector.new(scope_index)
|
|
204
222
|
}
|
|
223
|
+
collectors[:plugin_api_surface] = PluginApiSurface::Collector.new(scope_index) if plugin_api_surface
|
|
224
|
+
collectors
|
|
205
225
|
end
|
|
206
226
|
|
|
207
227
|
# A {RuleWalk::CollectorDriver} over a built-in collector set, for a
|
|
@@ -279,8 +299,11 @@ module Rigor
|
|
|
279
299
|
diagnostics = []
|
|
280
300
|
eval_ranges = receiver_eval_block_ranges(root)
|
|
281
301
|
lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
|
|
302
|
+
raise_shield = AlwaysRaisesShield.new(root)
|
|
282
303
|
Source::NodeWalker.each(root) do |node|
|
|
283
|
-
diagnostics.concat(
|
|
304
|
+
diagnostics.concat(
|
|
305
|
+
main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites, raise_shield)
|
|
306
|
+
)
|
|
284
307
|
end
|
|
285
308
|
diagnostics
|
|
286
309
|
end
|
|
@@ -300,18 +323,22 @@ module Rigor
|
|
|
300
323
|
shadow_verify_node_collectors(path, root, scope_index, collectors)
|
|
301
324
|
end
|
|
302
325
|
|
|
303
|
-
|
|
326
|
+
# Issue #2786 — the rules that type the call's receiver read it from one {CallFacts}, so the receiver chain is
|
|
327
|
+
# typed once per call node rather than once per rule.
|
|
328
|
+
def call_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil,
|
|
329
|
+
raise_shield = nil)
|
|
330
|
+
facts = CallFacts.new(node, scope_index)
|
|
304
331
|
[
|
|
305
|
-
undefined_method_diagnostic(path, node,
|
|
332
|
+
undefined_method_diagnostic(path, node, facts, lexical_sites),
|
|
306
333
|
unresolved_toplevel_diagnostic(path, node, scope_index, eval_ranges, lexical_sites),
|
|
307
|
-
wrong_arity_diagnostic(path, node,
|
|
308
|
-
argument_type_diagnostic(path, node,
|
|
309
|
-
nil_receiver_diagnostic(path, node,
|
|
334
|
+
wrong_arity_diagnostic(path, node, facts, lexical_sites),
|
|
335
|
+
argument_type_diagnostic(path, node, facts, lexical_sites),
|
|
336
|
+
nil_receiver_diagnostic(path, node, facts, lexical_sites),
|
|
310
337
|
dump_type_diagnostic(path, node, scope_index),
|
|
311
338
|
assert_type_diagnostic(path, node, scope_index),
|
|
312
|
-
always_raises_diagnostic(path, node,
|
|
339
|
+
always_raises_diagnostic(path, node, facts, lexical_sites, raise_shield),
|
|
313
340
|
raise_non_exception_diagnostic(path, node, scope_index),
|
|
314
|
-
visibility_mismatch_diagnostic(path, node,
|
|
341
|
+
visibility_mismatch_diagnostic(path, node, facts, lexical_sites)
|
|
315
342
|
].compact
|
|
316
343
|
end
|
|
317
344
|
|
|
@@ -407,6 +434,11 @@ module Rigor
|
|
|
407
434
|
end
|
|
408
435
|
end
|
|
409
436
|
|
|
437
|
+
# ADR-128 WD6 / ADR-130 slice 2 — `plugin.api-surface`, from its collector's results.
|
|
438
|
+
def plugin_api_surface_diagnostics(path, results)
|
|
439
|
+
PluginApiSurface.diagnostics(path, results)
|
|
440
|
+
end
|
|
441
|
+
|
|
410
442
|
# ADR-100 WD2 — `static.value-use.void`. Its value-context slot inspection rides the shared per-node
|
|
411
443
|
# {RuleWalk} like the other collectors, so the file is not re-traversed for it. Each result is a
|
|
412
444
|
# value-context use of a call whose author-declared `-> void` return the engine recovered to `top`.
|
|
@@ -740,17 +772,17 @@ module Rigor
|
|
|
740
772
|
class << self
|
|
741
773
|
private
|
|
742
774
|
|
|
743
|
-
def undefined_method_diagnostic(path, call_node,
|
|
775
|
+
def undefined_method_diagnostic(path, call_node, facts, lexical_sites = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
744
776
|
return nil if call_node.receiver.nil?
|
|
745
777
|
|
|
746
|
-
scope =
|
|
778
|
+
scope = facts.scope
|
|
747
779
|
return nil if scope.nil?
|
|
748
780
|
|
|
749
781
|
# The exemptions that hold whatever shape the receiver has. Ahead of the receiver-shape
|
|
750
782
|
# branch below because a plugin is consulted ONCE for the whole receiver, union included, so the
|
|
751
783
|
# gate belongs where that one consult is rather than duplicated into each shape's path.
|
|
752
784
|
#
|
|
753
|
-
# The union side of that placement
|
|
785
|
+
# The union side of that placement is reachable. The
|
|
754
786
|
# shape needs a non-nil union of at least two RBS-DECLARED classes — an undeclared arm is an
|
|
755
787
|
# ADR-26 open receiver and `union_arm_blocks_undefined_fire?` bails before the method lookup,
|
|
756
788
|
# which is why the earlier probes read silent and were misread as the gate being unreachable.
|
|
@@ -776,7 +808,7 @@ module Rigor
|
|
|
776
808
|
# below — preserving that keeps `&.` from newly firing on the
|
|
777
809
|
# non-nil constituent (which, for a cross-file project def, would
|
|
778
810
|
# be a working-code false positive).
|
|
779
|
-
receiver_type = safe_navigation_receiver(call_node,
|
|
811
|
+
receiver_type = safe_navigation_receiver(call_node, facts.receiver_type)
|
|
780
812
|
|
|
781
813
|
# #320 — the private-singleton-object idiom (`class << Merger = Object.new`). The body's methods
|
|
782
814
|
# are recorded on the constant's own name, but the receiver reads back as `Object`, so the
|
|
@@ -797,13 +829,6 @@ module Rigor
|
|
|
797
829
|
return union_undefined_method_diagnostic(path, call_node, receiver_type, scope, lexical_sites)
|
|
798
830
|
end
|
|
799
831
|
|
|
800
|
-
# ADR-26 — a plugin may declare a class "open": one
|
|
801
|
-
# known to respond beyond its RBS-declared method
|
|
802
|
-
# surface (e.g. `ActiveRecord::Relation`, which
|
|
803
|
-
# delegates an unbounded set of user-defined scopes to
|
|
804
|
-
# its model). Flagging an undefined method on a class
|
|
805
|
-
# with an open dynamic surface is unsound, so the rule
|
|
806
|
-
# skips it.
|
|
807
832
|
# An unbounded receiver surface: either a plugin-declared
|
|
808
833
|
# open receiver (ADR-26 — e.g. `ActiveRecord::Relation`), or
|
|
809
834
|
# a type Rigor synthesized (a missing-namespace module / a
|
|
@@ -839,6 +864,7 @@ module Rigor
|
|
|
839
864
|
return nil if method_def
|
|
840
865
|
|
|
841
866
|
return nil if last_resort_surface_answers?(receiver_type, class_name, call_node, scope, kind, lexical_sites)
|
|
867
|
+
return nil if open_project_singleton?(receiver_type, class_name, call_node, scope)
|
|
842
868
|
return nil if subclass_instance_may_answer?(rendered_receiver, class_name, call_node.name, scope,
|
|
843
869
|
call_node.receiver)
|
|
844
870
|
|
|
@@ -979,16 +1005,268 @@ module Rigor
|
|
|
979
1005
|
call_node: call_node)
|
|
980
1006
|
return true if module_mixin_receiver?(receiver_type, scope)
|
|
981
1007
|
return true if mixin_self_class_receiver?(call_node, scope)
|
|
982
|
-
return true if unknown_mixin_includer?(class_name, scope)
|
|
1008
|
+
return true if unknown_mixin_includer?(class_name, scope, kind)
|
|
983
1009
|
return true if object_mixin_may_define?(receiver_type, class_name, call_node.name, scope)
|
|
984
1010
|
return true if ancestry_declares_method?(scope, class_name, call_node.name, kind)
|
|
1011
|
+
return true if open_names_through_ancestors?(scope, class_name, kind)
|
|
1012
|
+
return true if foreign_singleton_through_ancestors?(scope, class_name, call_node.name, kind)
|
|
985
1013
|
|
|
986
1014
|
rbs_ancestor_may_answer?(class_name, call_node.name, scope, kind)
|
|
987
1015
|
end
|
|
988
1016
|
|
|
1017
|
+
# Issue #2793 — the class, or a project class or module on its chain, carries
|
|
1018
|
+
# `Scope::DiscoveryIndex::ENVELOPE_OPEN_NAMES_MARK`: a macro there defines methods under names its syntax
|
|
1019
|
+
# does not spell (`def_delegators :@x, *NAMES`), so no name is provably missing. A chain cut at its budget
|
|
1020
|
+
# never reaches here: {#ancestry_declares_method?} reads the same chain first and declines on the cut.
|
|
1021
|
+
# A non-literal `define_method` records its own side's twin of the mark instead, read beside it here.
|
|
1022
|
+
OPEN_NAMES_MARKS = {
|
|
1023
|
+
instance: [Scope::DiscoveryIndex::ENVELOPE_OPEN_NAMES_MARK,
|
|
1024
|
+
Scope::DiscoveryIndex::ENVELOPE_OPEN_INSTANCE_NAMES_MARK].freeze,
|
|
1025
|
+
singleton: [Scope::DiscoveryIndex::ENVELOPE_OPEN_NAMES_MARK,
|
|
1026
|
+
Scope::DiscoveryIndex::ENVELOPE_OPEN_SINGLETON_NAMES_MARK].freeze
|
|
1027
|
+
}.freeze
|
|
1028
|
+
private_constant :OPEN_NAMES_MARKS
|
|
1029
|
+
|
|
1030
|
+
def open_names_through_ancestors?(scope, class_name, kind)
|
|
1031
|
+
side = kind == :singleton ? :singleton : :instance
|
|
1032
|
+
marks = OPEN_NAMES_MARKS.fetch(side)
|
|
1033
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, side, :methods)
|
|
1034
|
+
# A project class with no `def` of its own is held as an external entry (#2094), so an external's
|
|
1035
|
+
# candidate names are asked too.
|
|
1036
|
+
found = chain.search(scope) do |entry|
|
|
1037
|
+
names = entry.external? ? entry.candidates : [entry.name]
|
|
1038
|
+
names.any? do |name|
|
|
1039
|
+
bucket = scope.parameter_envelopes_of(name)
|
|
1040
|
+
marks.any? { |mark| bucket.key?(mark) } ||
|
|
1041
|
+
(side == :instance && (computed_definer_called?(scope, name, bucket) ||
|
|
1042
|
+
hook_block_macro_defines?(scope, name)))
|
|
1043
|
+
end
|
|
1044
|
+
end
|
|
1045
|
+
!found.nil?
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
# Issue #3018 — a class object's singleton method that `def Foo.x`, `Foo.define_singleton_method(:x)` or a
|
|
1049
|
+
# `Foo.singleton_class.class_eval` def defines outside `Foo`'s body records no `:singleton` row, only an
|
|
1050
|
+
# envelope entry. The class-object path reads the definer's own entry
|
|
1051
|
+
# (`Scope::DiscoveryIndex::ENVELOPE_OBJECT_SINGLETON_KIND`) on the chain's class-object entries, the class and
|
|
1052
|
+
# its superclasses, so a subclass inherits it. An extended module's entry is instance-side: `extend M` adds
|
|
1053
|
+
# `M`'s instance methods, not a `def M.x`. It does not read the opaque `[:singleton, x]` entry beside it, which
|
|
1054
|
+
# a name-rewriting class-body macro (`my_attr :x`) files too without defining `x` on the class object.
|
|
1055
|
+
def foreign_singleton_through_ancestors?(scope, class_name, method_name, kind)
|
|
1056
|
+
return false unless kind == :singleton
|
|
1057
|
+
|
|
1058
|
+
key = [Scope::DiscoveryIndex::ENVELOPE_OBJECT_SINGLETON_KIND, method_name.to_sym]
|
|
1059
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :singleton, :methods)
|
|
1060
|
+
found = chain.search(scope, side: :singleton) do |entry|
|
|
1061
|
+
names = entry.external? ? entry.candidates : [entry.name]
|
|
1062
|
+
names.any? { |name| scope.parameter_envelopes_of(name).key?(key) }
|
|
1063
|
+
end
|
|
1064
|
+
!found.nil?
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# Issue #2901 — the body of `name` calls, on `self`, a method its singleton chain resolves to a computed
|
|
1068
|
+
# definer (`Scope::DiscoveryIndex::ENVELOPE_COMPUTED_DEFINER_KIND`): a class macro of its own, a
|
|
1069
|
+
# superclass's or an extended module's, which runs `define_method` under a name no table holds. The first
|
|
1070
|
+
# entry on that chain carrying the macro's definer entry answers; a closer override that defines nothing is
|
|
1071
|
+
# not looked for, so it leaves the class open.
|
|
1072
|
+
#
|
|
1073
|
+
# Issue #2975 — the definer entry carries its side: the class's own singleton entry answers with a singleton
|
|
1074
|
+
# method (`def self.my_attr`), an extended module's entry with an instance method, so an instance `def my_attr`
|
|
1075
|
+
# of the class itself opens nothing. A macro patched onto `Module` (or `Class`, `Object`, `Kernel`,
|
|
1076
|
+
# `BasicObject`) as an instance method is on every class object's singleton chain past its own classes.
|
|
1077
|
+
def computed_definer_called?(scope, name, bucket)
|
|
1078
|
+
macros = bucket.each_key.filter_map do |key|
|
|
1079
|
+
key.last if key.is_a?(Array) && key.first == Scope::DiscoveryIndex::ENVELOPE_BODY_CALL_KIND
|
|
1080
|
+
end
|
|
1081
|
+
return false if macros.empty?
|
|
1082
|
+
|
|
1083
|
+
chain = Scope::ResolutionChain.for(scope, name.to_s, :singleton, :methods)
|
|
1084
|
+
found = macros.any? do |macro|
|
|
1085
|
+
!chain.search(scope) { |entry| macro_entry_defines?(scope, entry, macro) }.nil? ||
|
|
1086
|
+
universal_macro_defines?(scope, macro)
|
|
1087
|
+
end
|
|
1088
|
+
found || hook_extended_macro_defines?(scope, name, macros)
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
# Issue #2975 — a module that a class on `name`'s superclass chain includes or prepends directly, and whose
|
|
1092
|
+
# `included` / `prepended` hook extends its includer with a module (`def self.included(base) =
|
|
1093
|
+
# base.extend(ClassMethods)`), puts that module's instance methods on `name`'s singleton chain, which records no
|
|
1094
|
+
# edge for it. Ruby runs the hook for the direct includer only, so a module included through another module does
|
|
1095
|
+
# not count; the chain's levels flatten a class's transitive mixins, so each level class's own mixin list is
|
|
1096
|
+
# read, one hop, never followed further. Each mixin and each extended constant resolves to the first candidate
|
|
1097
|
+
# the project declares, the innermost cref first, whether or not RBS declares the modules; a concern's
|
|
1098
|
+
# `ClassMethods` resolves as `const_get` does instead (#2986).
|
|
1099
|
+
def hook_extended_macro_defines?(scope, name, macros)
|
|
1100
|
+
definers = macro_definers(macros)
|
|
1101
|
+
direct_hook_mixins(scope, name).any? { |mixin, _edges| hook_extension_defines?(scope, mixin, definers) }
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
# Issue #2986 — an ActiveSupport::Concern's `included do … end` block runs in the class body of a class that
|
|
1105
|
+
# includes the concern directly, and its `prepended do … end` block in one that prepends it, so a macro call
|
|
1106
|
+
# in the block is read as that class's own, against the module the concern extends it with. A call in the
|
|
1107
|
+
# concern's own module body is no includer's: Ruby raises there as the module loads.
|
|
1108
|
+
#
|
|
1109
|
+
# Issue #2992 — Ruby runs the block in every direct includer, a module included, so the read is made for each
|
|
1110
|
+
# entry of the receiver's chain (`owner`) against that entry's own mixins, one hop. A concern included into a
|
|
1111
|
+
# concern after its `extend ActiveSupport::Concern` runs no block there: `Concern#append_features` records it as
|
|
1112
|
+
# a dependency, and the outer concern's final includer includes it, on that includer's own edge
|
|
1113
|
+
# ({#with_concern_dependencies}). Only a mixin whose one `include` / `prepend` statement is such a dependency
|
|
1114
|
+
# is left out of `owner`'s own read: one written ahead of the `extend`, or again in a reopening that may load
|
|
1115
|
+
# first, is an ordinary include there.
|
|
1116
|
+
def hook_block_macro_defines?(scope, owner)
|
|
1117
|
+
own_mixin_edges(scope, owner, deferred: true).any? do |mixin, edges|
|
|
1118
|
+
macros = scope.parameter_envelopes_of(mixin).each_key.filter_map do |key|
|
|
1119
|
+
key[2] if key.is_a?(Array) && key.first == Scope::DiscoveryIndex::ENVELOPE_HOOK_BODY_CALL_KIND &&
|
|
1120
|
+
edges.include?(Scope::ResolutionChain.effective_hook(scope.discovery, mixin, key[1]))
|
|
1121
|
+
end
|
|
1122
|
+
!macros.empty? && hook_extension_defines?(scope, mixin, macro_definers(macros))
|
|
1123
|
+
end
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
def macro_definers(macros)
|
|
1127
|
+
macros.map { |macro| [Scope::DiscoveryIndex::ENVELOPE_COMPUTED_DEFINER_KIND, :instance, macro] }
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
# Each module a class on `name`'s superclass chain includes or prepends directly, resolved, with the hook
|
|
1131
|
+
# blocks its edges run: `:included` for an `include`, `:prepended` for a `prepend`; a concern's dependencies
|
|
1132
|
+
# follow it on the same edges.
|
|
1133
|
+
def direct_hook_mixins(scope, name)
|
|
1134
|
+
chain = Scope::ResolutionChain.for(scope, name.to_s, :instance, :methods)
|
|
1135
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
1136
|
+
chain.level_classes.compact.flat_map { |klass| own_mixin_edges(scope, klass) }
|
|
1137
|
+
end
|
|
1138
|
+
|
|
1139
|
+
# The modules `owner` itself includes or prepends, as {#direct_hook_mixins} gives them. With `deferred:`, a
|
|
1140
|
+
# provable dependency of a concern `owner` is left out ({#hook_block_macro_defines?}).
|
|
1141
|
+
def own_mixin_edges(scope, owner, deferred: false)
|
|
1142
|
+
mixins = scope.includes_of(owner)
|
|
1143
|
+
prepends = scope.prepends_of(owner)
|
|
1144
|
+
deferred_mixins = deferred ? deferred_dependencies(scope, owner, mixins) : NO_DEFERRED
|
|
1145
|
+
mixins.uniq.flat_map do |raw|
|
|
1146
|
+
mixin = declared_constant(scope, owner, raw)
|
|
1147
|
+
next [] unless mixin
|
|
1148
|
+
|
|
1149
|
+
edges = []
|
|
1150
|
+
edges << :prepended if prepends.include?(raw)
|
|
1151
|
+
edges << :included if mixins.count(raw) > prepends.count(raw)
|
|
1152
|
+
next [] if deferred_mixins.include?(raw)
|
|
1153
|
+
|
|
1154
|
+
with_concern_dependencies(scope, mixin).map { |member| [member, edges] }
|
|
1155
|
+
end
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
NO_DEFERRED = [].freeze
|
|
1159
|
+
private_constant :NO_DEFERRED
|
|
1160
|
+
|
|
1161
|
+
# The spellings among `owner`'s mixins that name a concern dependency of `owner`, when `owner` is declared in
|
|
1162
|
+
# one file: a reopening may write the same `include` without the `extend`, and load first.
|
|
1163
|
+
def deferred_dependencies(scope, owner, mixins)
|
|
1164
|
+
sites = scope.discovery.discovered_class_sources[owner.to_s]
|
|
1165
|
+
return NO_DEFERRED if sites.nil? || sites.size > 1
|
|
1166
|
+
|
|
1167
|
+
resolver = Scope::ResolutionChain.resolver_for(scope, :methods)
|
|
1168
|
+
dependencies = Scope::ResolutionChain.concern_dependencies(scope.discovery, resolver, owner)
|
|
1169
|
+
return NO_DEFERRED if dependencies.empty?
|
|
1170
|
+
|
|
1171
|
+
Scope::ResolutionChain.dependency_spellings(scope.discovery, owner).select do |raw|
|
|
1172
|
+
mixins.include?(raw) && dependencies.include?(resolver.resolve_declared(owner, raw))
|
|
1173
|
+
end
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
# Issue #2992 — `mixin` and, when it is a concern, the concerns it includes or prepends, at any depth through
|
|
1177
|
+
# concerns ({Scope::ResolutionChain.concern_dependencies}): `Concern#append_features` (or `prepend_features`)
|
|
1178
|
+
# includes (or prepends) each of them into the final includer ahead of `mixin` itself, on the includer's own
|
|
1179
|
+
# edge to `mixin`, whatever edge each was given. A reader files each one's sites as it reads its envelopes.
|
|
1180
|
+
def with_concern_dependencies(scope, mixin)
|
|
1181
|
+
resolver = Scope::ResolutionChain.resolver_for(scope, :methods)
|
|
1182
|
+
[mixin, *Scope::ResolutionChain.concern_dependencies(scope.discovery, resolver, mixin)]
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
def hook_extension_defines?(scope, mixin, definers)
|
|
1186
|
+
hook_extends(scope, mixin).any? do |written, lookup|
|
|
1187
|
+
target = if lookup == :const_get then const_get_constant(scope, mixin, written)
|
|
1188
|
+
else declared_constant(scope, mixin, written)
|
|
1189
|
+
end
|
|
1190
|
+
record_hook_target_edges(scope, mixin, written, lookup) if Analysis::DependencyRecorder.active?
|
|
1191
|
+
target && definers.any? { |definer| scope.parameter_envelopes_of(target).key?(definer) }
|
|
1192
|
+
end
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
# Issue #2992 — the read depends on the extended module's files whether or not it resolved: an empty
|
|
1196
|
+
# `module ClassMethods; end` is no project class yet, and the edit that gives it the definer makes it one. So
|
|
1197
|
+
# every name the lookup tried files its declaring sites, and the constant's last segment its negative class
|
|
1198
|
+
# edge, which a NEW file declaring it fires.
|
|
1199
|
+
def record_hook_target_edges(scope, mixin, written, lookup)
|
|
1200
|
+
candidates = if lookup == :const_get
|
|
1201
|
+
chain = Scope::ResolutionChain.for(scope, mixin, :instance, :methods)
|
|
1202
|
+
chain.entries.flat_map { |entry| entry.external? ? entry.candidates : [entry.name] }
|
|
1203
|
+
.map { |name| "#{name}::#{written}" } << written
|
|
1204
|
+
else
|
|
1205
|
+
own = written.start_with?("::") ? [] : ["#{mixin}::#{written}"]
|
|
1206
|
+
own + scope.ancestor_name_candidates(mixin, written)
|
|
1207
|
+
end
|
|
1208
|
+
candidates.each { |candidate| Scope::ResolutionChain.record_class(scope, candidate.delete_prefix("::")) }
|
|
1209
|
+
Analysis::DependencyRecorder.read_last_segment(:class, written)
|
|
1210
|
+
end
|
|
1211
|
+
|
|
1212
|
+
# The constant `raw`, written in `owner`'s body, as the first candidate the project declares: `Owner::Raw`
|
|
1213
|
+
# first, since `owner` is the innermost cref there, then the enclosing namespaces. Nil when none is declared.
|
|
1214
|
+
def declared_constant(scope, owner, raw)
|
|
1215
|
+
candidates = scope.ancestor_name_candidates(owner, raw)
|
|
1216
|
+
candidates = ["#{owner}::#{raw}", *candidates] unless raw.start_with?("::")
|
|
1217
|
+
candidates.find { |candidate| scope.known_user_class?(candidate) }
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
# Issue #2986 — `owner.const_get(raw)`, as `Concern#append_features` looks `ClassMethods` up: `owner` and its
|
|
1221
|
+
# ancestors, then `Object`, never the namespaces enclosing `owner`. Issue #2997 — a `class_methods` block's
|
|
1222
|
+
# `Name::ClassMethods` counts only for a concern `Name` ({Scope::ResolutionChain.const_get_candidate?}).
|
|
1223
|
+
def const_get_constant(scope, owner, raw)
|
|
1224
|
+
chain = Scope::ResolutionChain.for(scope, owner, :instance, :methods)
|
|
1225
|
+
found = chain.search(scope) do |entry|
|
|
1226
|
+
names = entry.external? ? entry.candidates : [entry.name]
|
|
1227
|
+
names.map { |name| [name, "#{name}::#{raw}"] }.find do |name, candidate|
|
|
1228
|
+
scope.known_user_class?(candidate) &&
|
|
1229
|
+
Scope::ResolutionChain.const_get_candidate?(scope.discovery, name, candidate)
|
|
1230
|
+
end&.last
|
|
1231
|
+
end
|
|
1232
|
+
found || (raw if scope.known_user_class?(raw))
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1235
|
+
# The `[written, lookup]` pairs of a module's hook-extend entries; `lookup` is `:const_get` for a concern's.
|
|
1236
|
+
# Issue #3005 — a gated concern-block entry counts only for a concern `owner`.
|
|
1237
|
+
def hook_extends(scope, owner)
|
|
1238
|
+
scope.parameter_envelopes_of(owner).each_key.filter_map do |key|
|
|
1239
|
+
next unless key.is_a?(Array) && key.first == Scope::DiscoveryIndex::ENVELOPE_HOOK_EXTEND_KIND
|
|
1240
|
+
next unless Scope::ResolutionChain.effective_hook(scope.discovery, owner, key[2])
|
|
1241
|
+
|
|
1242
|
+
key[1, 2]
|
|
1243
|
+
end
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
# A macro's owner holds the macro's `def`, so it is never an external entry.
|
|
1247
|
+
def macro_entry_defines?(scope, entry, macro)
|
|
1248
|
+
!entry.external? &&
|
|
1249
|
+
scope.parameter_envelopes_of(entry.name).key?([Scope::DiscoveryIndex::ENVELOPE_COMPUTED_DEFINER_KIND,
|
|
1250
|
+
entry.side, macro])
|
|
1251
|
+
end
|
|
1252
|
+
|
|
1253
|
+
# The classes past a class object's own singleton chain, whose instance methods it answers. A module's chain
|
|
1254
|
+
# does not reach `Class`, but a module body calling a `Class` macro raises as it loads, so it is not told apart.
|
|
1255
|
+
UNIVERSAL_MACRO_OWNERS = %w[Class Module Object Kernel BasicObject].freeze
|
|
1256
|
+
private_constant :UNIVERSAL_MACRO_OWNERS
|
|
1257
|
+
|
|
1258
|
+
def universal_macro_defines?(scope, macro)
|
|
1259
|
+
definer = [Scope::DiscoveryIndex::ENVELOPE_COMPUTED_DEFINER_KIND, :instance, macro]
|
|
1260
|
+
UNIVERSAL_MACRO_OWNERS.any? { |owner| scope.parameter_envelopes_of(owner).key?(definer) }
|
|
1261
|
+
end
|
|
1262
|
+
|
|
989
1263
|
# Issue #1722 — a `method_missing`, or a project mixin, on one of the class's RBS ancestors
|
|
990
1264
|
# ({RbsReceiverSurface}). Last for {#ancestry_declares_method?}'s reason: it walks the class graph.
|
|
1265
|
+
# Issue #2792 — a `method_missing` RBS declares on the class or an ancestor, other than the universal one.
|
|
991
1266
|
def rbs_ancestor_may_answer?(class_name, method_name, scope, kind)
|
|
1267
|
+
side = kind == :singleton ? :singleton : :instance
|
|
1268
|
+
return true if RbsReceiverSurface.rbs_missing_hook?(scope, class_name, side)
|
|
1269
|
+
|
|
992
1270
|
if kind == :singleton
|
|
993
1271
|
RbsReceiverSurface.singleton_hook?(scope, class_name) ||
|
|
994
1272
|
RbsReceiverSurface.singleton_mixin_may_answer?(scope, class_name, method_name)
|
|
@@ -1342,14 +1620,14 @@ module Rigor
|
|
|
1342
1620
|
# (`METACLASS_ARMS`, "a `plugin_class : Class` really holds a `Plugin` subclass with `.manifest`");
|
|
1343
1621
|
# the scalar rule enumerated `Module`'s own RBS instead and reported them.
|
|
1344
1622
|
#
|
|
1345
|
-
# Kept beside `unbounded_receiver_surface?` rather than folded into it: that predicate has
|
|
1346
|
-
#
|
|
1623
|
+
# Kept beside `unbounded_receiver_surface?` rather than folded into it: that predicate has other
|
|
1624
|
+
# callers, and #742 had no evidence that any of them spoke on these receivers.
|
|
1347
1625
|
#
|
|
1348
1626
|
# Issue #879 supplied it for exactly one — `call.wrong-arity`, which decided `v.hexdigest(1, 2, 3)`
|
|
1349
1627
|
# against `Digest::Instance`'s declaration on the same run where `v.no_such_method_zzz` was declined
|
|
1350
1628
|
# as unenumerable. One receiver, one run, two answers about whether that surface can be read. The
|
|
1351
1629
|
# reporter could make no other caller speak: `raise` on a module- or `Class`-typed operand is silent,
|
|
1352
|
-
# and `Class`-typed `.new` with a wrong arity is silent too, so the other
|
|
1630
|
+
# and `Class`-typed `.new` with a wrong arity is silent too, so the other callers stay on
|
|
1353
1631
|
# `unbounded_receiver_surface?` until something demonstrates otherwise.
|
|
1354
1632
|
#
|
|
1355
1633
|
# Arity takes {#unenumerable_receiver_shape?}, not this whole predicate: ADR-26's open receivers are
|
|
@@ -1382,13 +1660,19 @@ module Rigor
|
|
|
1382
1660
|
# answers, that never reaches a verdict — depend on `Widget`'s declaring file.
|
|
1383
1661
|
# `dependency_recorder_spec` caught it here exactly as it caught #723's walk.
|
|
1384
1662
|
#
|
|
1385
|
-
#
|
|
1386
|
-
#
|
|
1387
|
-
|
|
1663
|
+
# A constant the environment does not know is read off the receiver's OWN includes only. Issue #2991 — a
|
|
1664
|
+
# mixin no name spells (`include(*MODS)`, `include helper`) opens the receiver when it sits anywhere on its
|
|
1665
|
+
# instance-side chain, its own level or an ancestor's: the chain marks it (`ResolutionChain#unnamed_mixin?`),
|
|
1666
|
+
# and any name may come from the unlisted module (a hook's `base.include M` is read so too). `self.include M`
|
|
1667
|
+
# and a receiverless `send(:include, M)` are recorded as `include M` is (#2995), so the ordinary lookup reads
|
|
1668
|
+
# `M`. A mixin whose ORDER alone is unknown (`include M; prepend M`) has its module recorded, so it opens
|
|
1669
|
+
# nothing. A class-object receiver reads its singleton chain instead ({#singleton_mixin_may_answer?}).
|
|
1670
|
+
def unknown_mixin_includer?(class_name, scope, kind)
|
|
1388
1671
|
includes = scope.includes_of(class_name)
|
|
1389
|
-
return
|
|
1672
|
+
return true if includes.any? { |raw| !known_mixin?(raw, class_name, scope) }
|
|
1673
|
+
return false if kind == :singleton
|
|
1390
1674
|
|
|
1391
|
-
|
|
1675
|
+
Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :methods).unnamed_mixin?
|
|
1392
1676
|
end
|
|
1393
1677
|
|
|
1394
1678
|
def known_mixin?(raw_name, class_name, scope)
|
|
@@ -1451,14 +1735,16 @@ module Rigor
|
|
|
1451
1735
|
end
|
|
1452
1736
|
|
|
1453
1737
|
# ADR-67 WD6b — the argument-position analogue: true when `arg` is rooted at an inferred-parameter
|
|
1454
|
-
# local. The argument-type-mismatch rule declines on it for the same lower-bound reason.
|
|
1738
|
+
# local. The argument-type-mismatch rule declines on it for the same lower-bound reason. `scope` is the
|
|
1739
|
+
# call's, and the receiver and any earlier argument run before `arg` and may rebind what a conditional's
|
|
1740
|
+
# predicate in it reads (`m((c = false; 1), c ? param : "x")`), so a predicate there is never folded.
|
|
1455
1741
|
def inferred_param_argument?(arg, scope)
|
|
1456
|
-
inferred_param_rooted?(arg, scope)
|
|
1742
|
+
inferred_param_rooted?(arg, scope, fold: false)
|
|
1457
1743
|
end
|
|
1458
1744
|
|
|
1459
1745
|
# ADR-67 WD6b — see {CheckRules::InferredParamGuard} for the shared root-walk contract.
|
|
1460
|
-
def inferred_param_rooted?(node, scope)
|
|
1461
|
-
InferredParamGuard.rooted?(node, scope)
|
|
1746
|
+
def inferred_param_rooted?(node, scope, fold: true)
|
|
1747
|
+
InferredParamGuard.rooted?(node, scope, fold: fold)
|
|
1462
1748
|
end
|
|
1463
1749
|
|
|
1464
1750
|
def constant_class_name(value)
|
|
@@ -1498,8 +1784,8 @@ module Rigor
|
|
|
1498
1784
|
# as a per-instance singleton method and answers the rest through a private `method_missing` (ostruct 0.6), so
|
|
1499
1785
|
# no signature can enumerate its surface. The only other declaration in the environment is json's add-on
|
|
1500
1786
|
# reopening (`json_create` / `as_json` / `to_json`), which made the class read as fully declared and every
|
|
1501
|
-
# field access a `call.undefined-method`. `data/core_overlay/ostruct.rbs` declares that `method_missing
|
|
1502
|
-
#
|
|
1787
|
+
# field access a `call.undefined-method`. `data/core_overlay/ostruct.rbs` declares that `method_missing`; since
|
|
1788
|
+
# #2792 that alone keeps `call.undefined-method` silent, but the rules below still need the class named. No load
|
|
1503
1789
|
# gate is needed: the core overlay loads into every environment that has RBS at all, so the list is active
|
|
1504
1790
|
# whenever the declaration it protects is. Both sides of the class are open, as with
|
|
1505
1791
|
# {GEM_OVERLAY_OPEN_RECEIVERS}, and an inherited Object / Kernel signature stops deciding `call.wrong-arity`
|
|
@@ -1530,6 +1816,97 @@ module Rigor
|
|
|
1530
1816
|
ruby_library_reopen?(class_name, scope)
|
|
1531
1817
|
end
|
|
1532
1818
|
|
|
1819
|
+
# Issue #2438 — a class-method call on a class the project's own signatures name but whose source leaves
|
|
1820
|
+
# the singleton surface open. Before `rigor sig-gen --write` such a class was source-only and an
|
|
1821
|
+
# unresolved call on it was silent; the sidecar must not turn the same call into a finding. Two shapes:
|
|
1822
|
+
#
|
|
1823
|
+
# - `C.instance` where `C`, or a project class up its superclass chain, `include`s the stdlib `Singleton`:
|
|
1824
|
+
# the answer is `SingletonMixinDispatch.answers?`, the predicate the typer itself uses, so the rule never
|
|
1825
|
+
# reports a call the engine typed. The class's other members are irrelevant: the mixin's hook defines
|
|
1826
|
+
# `instance`.
|
|
1827
|
+
# - every project declaration of `C` is member-less (`RbsLoader#memberless_project_class?`) AND the source
|
|
1828
|
+
# leaves `C` open ({#open_source_class?}). A member-less declaration does not describe the class's
|
|
1829
|
+
# surface, but a class whose whole definition Rigor can see in `paths:` is still closed, so a typo on it
|
|
1830
|
+
# keeps firing. Instance-side calls keep being checked; declaring any member re-closes the class.
|
|
1831
|
+
def open_project_singleton?(receiver_type, class_name, call_node, scope)
|
|
1832
|
+
return false unless receiver_type.is_a?(Type::Singleton)
|
|
1833
|
+
return true if Inference::MethodDispatcher::SingletonMixinDispatch.answers?(
|
|
1834
|
+
scope, receiver_type, call_node.name, call_node.arguments.nil?
|
|
1835
|
+
)
|
|
1836
|
+
|
|
1837
|
+
loader = scope.environment&.rbs_loader
|
|
1838
|
+
return false if loader.nil? || !loader.respond_to?(:memberless_project_class?)
|
|
1839
|
+
|
|
1840
|
+
loader.memberless_project_class?(class_name) && open_source_class?(class_name, scope, loader)
|
|
1841
|
+
end
|
|
1842
|
+
|
|
1843
|
+
OPENING_HOOKS = %i[inherited included extended prepended method_missing respond_to_missing?].freeze
|
|
1844
|
+
private_constant :OPENING_HOOKS
|
|
1845
|
+
|
|
1846
|
+
CLOSED_ROOTS = Set["Object", "BasicObject"].freeze
|
|
1847
|
+
private_constant :CLOSED_ROOTS
|
|
1848
|
+
|
|
1849
|
+
# Issue #2438 — whether a class's singleton surface may hold methods its source in `paths:` does not show,
|
|
1850
|
+
# read off `Scope::ResolutionChain` (`:constants` flavor, so a project module that holds only constants is
|
|
1851
|
+
# still a project entry). Open when the class, or a project class up its superclass chain:
|
|
1852
|
+
#
|
|
1853
|
+
# 1. `extend`s a module (any singleton-side entry beyond the classes' own singletons), or records an
|
|
1854
|
+
# `inherited` / `included` / `extended` / `prepended` hook or `method_missing` / `respond_to_missing?`.
|
|
1855
|
+
# A plain `include` or `prepend` adds instance methods only, so it leaves the singleton side closed;
|
|
1856
|
+
# 2. has no `class` / `module` body of its own inside `paths:` (`class Ext::Foo` names `Ext` without one);
|
|
1857
|
+
# 3. or the chain reaches a class the project does not define, other than `Object` / `BasicObject`: an
|
|
1858
|
+
# external superclass (`ApplicationRecord < ActiveRecord::Base`, `MyError < StandardError`), a
|
|
1859
|
+
# superclass written as an expression, or — where the source writes no superclass for the last class —
|
|
1860
|
+
# the superclass its member-less declaration writes, unless that is a project class.
|
|
1861
|
+
#
|
|
1862
|
+
# A definition or reopening in a file outside `paths:` cannot be seen, so a class whose body is inside
|
|
1863
|
+
# `paths:` and whose methods live only outside it counts as closed.
|
|
1864
|
+
def open_source_class?(class_name, scope, loader)
|
|
1865
|
+
name = class_name.to_s.delete_prefix("::")
|
|
1866
|
+
instance = Scope::ResolutionChain.for(scope, name, :instance, :constants)
|
|
1867
|
+
singleton = Scope::ResolutionChain.for(scope, name, :singleton, :constants)
|
|
1868
|
+
[instance, singleton].each { |chain| chain.record(scope) }
|
|
1869
|
+
classes = instance.level_classes.compact
|
|
1870
|
+
return true if classes.empty? || instance.truncated?
|
|
1871
|
+
|
|
1872
|
+
sources = scope.discovered_class_sources
|
|
1873
|
+
return true if classes.any? { |klass| (sources[klass] || []).empty? || opening_hook?(klass, scope) }
|
|
1874
|
+
return true if opening_entry?(instance, classes, mixins: false) ||
|
|
1875
|
+
opening_entry?(singleton, classes, mixins: true)
|
|
1876
|
+
|
|
1877
|
+
open_tail?(instance, classes.last, scope, loader)
|
|
1878
|
+
end
|
|
1879
|
+
|
|
1880
|
+
# An external superclass other than `Object` / `BasicObject` opens either side; a module entry opens the
|
|
1881
|
+
# chain only where `mixins` is set (the singleton side, where it is an `extend`).
|
|
1882
|
+
def opening_entry?(chain, classes, mixins:)
|
|
1883
|
+
chain.entries.any? do |entry|
|
|
1884
|
+
if entry.external? && entry.superclass_edge
|
|
1885
|
+
!CLOSED_ROOTS.include?(entry.raw.to_s.delete_prefix("::"))
|
|
1886
|
+
else
|
|
1887
|
+
mixins && !classes.include?(entry.name)
|
|
1888
|
+
end
|
|
1889
|
+
end
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1892
|
+
def opening_hook?(klass, scope)
|
|
1893
|
+
OPENING_HOOKS.any? do |hook|
|
|
1894
|
+
scope.discovered_method?(klass, hook, :singleton) || scope.discovered_method?(klass, hook, :instance)
|
|
1895
|
+
end
|
|
1896
|
+
end
|
|
1897
|
+
|
|
1898
|
+
# The last class's own superclass, which the chain does not name: an expression the tables cannot resolve,
|
|
1899
|
+
# or one only the member-less declaration writes (the chain ends at the implicit `Object` exactly when
|
|
1900
|
+
# the source writes none).
|
|
1901
|
+
def open_tail?(chain, last, scope, loader)
|
|
1902
|
+
return true if chain.opaque_tail?(scope)
|
|
1903
|
+
|
|
1904
|
+
raw = loader.memberless_project_superclass(last)
|
|
1905
|
+
return false if raw.nil? || CLOSED_ROOTS.include?(raw.delete_prefix("::"))
|
|
1906
|
+
|
|
1907
|
+
!scope.discovered_classes.key?(raw.delete_prefix("::"))
|
|
1908
|
+
end
|
|
1909
|
+
|
|
1533
1910
|
# Issue #2174 — `class Matrix` reopened with one `#:`-annotated method, and no `matrix` signatures
|
|
1534
1911
|
# loaded, gives Matrix a declaration holding only that method ({RbsLoader#ruby_library_reopen?}).
|
|
1535
1912
|
# `Matrix.identity(2)` is correct Ruby, so the class's surface is treated as open: `call.undefined-method`
|
|
@@ -1549,38 +1926,51 @@ module Rigor
|
|
|
1549
1926
|
registry.open_receiver?(class_name)
|
|
1550
1927
|
end
|
|
1551
1928
|
|
|
1552
|
-
# Whether THIS run's RBS environment actually includes
|
|
1553
|
-
#
|
|
1554
|
-
# constant's comment)
|
|
1555
|
-
#
|
|
1556
|
-
#
|
|
1929
|
+
# Whether THIS run's RBS environment actually includes Rigor's OWN partial declaration of
|
|
1930
|
+
# ACTIVESUPPORT — {GEM_OVERLAY_OPEN_RECEIVERS} membership alone must not grant the exemption (see
|
|
1931
|
+
# that constant's comment), and both members of that list are `ActiveSupport` classes, so the gate
|
|
1932
|
+
# asks for the activesupport overlay specifically. Issue #2857 added a SECOND bundled overlay
|
|
1933
|
+
# (`addressable`, eligible under `:rbs_collection` coverage), and a collection project can load it
|
|
1934
|
+
# while the activesupport overlay stays unloaded — keying on "any `data/gem_overlay/` directory"
|
|
1935
|
+
# would then silence `ActiveSupport::Duration#bar`, the ADR-26 false negative this gate exists to
|
|
1936
|
+
# prevent.
|
|
1557
1937
|
#
|
|
1558
|
-
#
|
|
1559
|
-
#
|
|
1560
|
-
#
|
|
1561
|
-
# source lives under that root;
|
|
1562
|
-
# - the bundled plugin twin's own `sig/`, when a project wires it through `signature_paths:` instead
|
|
1563
|
-
# of `plugins:`. Before #672 that route loaded BOTH halves and `Duration` collapsed outright, so
|
|
1564
|
-
# nothing fired and nothing needed protecting; now the overlay stands down and the twin's
|
|
1565
|
-
# declaration is the one that loads — with no manifest behind it, so `open_receiver?` above stays
|
|
1566
|
-
# false and this is the only thing standing between a real `Duration` receiver and a false
|
|
1567
|
-
# `call.undefined-method` on every member the declaration omits.
|
|
1938
|
+
# `RbsLoader#signature_paths` is the same list `Environment.for_project` builds from
|
|
1939
|
+
# `resolved_paths + plugin_sig_paths + gem_sig_paths + collection_paths + overlay_paths`, and
|
|
1940
|
+
# two of its members can carry the activesupport declaration:
|
|
1568
1941
|
#
|
|
1569
|
-
#
|
|
1570
|
-
#
|
|
1571
|
-
#
|
|
1572
|
-
#
|
|
1573
|
-
#
|
|
1942
|
+
# - `data/gem_overlay/activesupport/` itself — a member if and only if `gem_overlay_paths` picked
|
|
1943
|
+
# it (the gem is locked, its coverage source admits the overlay — `:missing` for activesupport —
|
|
1944
|
+
# and its plugin twin is NOT reachable, ADR-72). {RbsLoader.gem_overlay_sig_paths} rebuilds the
|
|
1945
|
+
# exact Pathname `for_project` appended, so a plain `include?` is the test: no other signature
|
|
1946
|
+
# source can occupy that path;
|
|
1947
|
+
# - the bundled activesupport plugin twin's own `sig/`, when a project wires it through
|
|
1948
|
+
# `signature_paths:` instead of `plugins:`. Before #672 that route loaded BOTH halves and
|
|
1949
|
+
# `Duration` collapsed outright, so nothing fired and nothing needed protecting; now the overlay
|
|
1950
|
+
# stands down and the twin's declaration is the one that loads — with no manifest behind it, so
|
|
1951
|
+
# `open_receiver?` above stays false and this is the only thing standing between a real
|
|
1952
|
+
# `Duration` receiver and a false `call.undefined-method` on every member the declaration
|
|
1953
|
+
# omits. {RbsLoader.gem_overlay_twin_signatures_loaded?} enumerates `GEM_OVERLAY_PLUGIN_IDS`'
|
|
1954
|
+
# values, which today is only the activesupport twin; a second overlay twin would need the same
|
|
1955
|
+
# per-gem keying this method now applies to the directory arm.
|
|
1574
1956
|
#
|
|
1575
|
-
#
|
|
1576
|
-
#
|
|
1577
|
-
#
|
|
1957
|
+
# When the PLUGIN itself is loaded the first arm goes false (the overlay stands down) and the
|
|
1958
|
+
# second goes true — its `sig/` rides in as a plugin signature path — but nothing here decides
|
|
1959
|
+
# that case: `open_receiver?`'s manifest-driven check short-circuits above it. The two answers
|
|
1960
|
+
# agree, which is the point; this predicate asks only whether the Rigor-authored activesupport
|
|
1961
|
+
# declaration is in the environment, never how the project asked for it.
|
|
1962
|
+
#
|
|
1963
|
+
# The two arms are asked in that order, over the WHOLE list each time, rather than interleaved
|
|
1964
|
+
# per path: the overlay arm is a Pathname comparison (plus the two `File.directory?` probes
|
|
1965
|
+
# {RbsLoader.gem_overlay_sig_paths} needs to rebuild the path) and the twin arm canonicalises
|
|
1966
|
+
# paths on the filesystem, so an overlay-only run pays the cheaper arm first.
|
|
1578
1967
|
def gem_overlay_loaded?(scope)
|
|
1579
1968
|
loader = scope.environment&.rbs_loader
|
|
1580
1969
|
return false if loader.nil? || !loader.respond_to?(:signature_paths)
|
|
1581
1970
|
|
|
1582
1971
|
paths = loader.signature_paths
|
|
1583
|
-
|
|
1972
|
+
activesupport_overlay = Rigor::Environment::RbsLoader.gem_overlay_sig_paths(["activesupport"])
|
|
1973
|
+
paths.any? { |path| activesupport_overlay.include?(path) } ||
|
|
1584
1974
|
Rigor::Environment::RbsLoader.gem_overlay_twin_signatures_loaded?(paths)
|
|
1585
1975
|
end
|
|
1586
1976
|
|
|
@@ -1663,6 +2053,16 @@ module Rigor
|
|
|
1663
2053
|
method_def
|
|
1664
2054
|
end
|
|
1665
2055
|
|
|
2056
|
+
# Issue #2786 — {#trustworthy_signature} on the call's receiver as {CallFacts} types it, resolved once per
|
|
2057
|
+
# call node: `call.wrong-arity` and `call.argument-type-mismatch` both ask it with that receiver type and its
|
|
2058
|
+
# concrete class.
|
|
2059
|
+
def receiver_signature(facts)
|
|
2060
|
+
facts.receiver_signature do
|
|
2061
|
+
receiver_type = facts.receiver_type
|
|
2062
|
+
trustworthy_signature(receiver_type, concrete_class_name(receiver_type), facts.call_node, facts.scope)
|
|
2063
|
+
end
|
|
2064
|
+
end
|
|
2065
|
+
|
|
1666
2066
|
def unauthoritative_inherited_signature?(class_name, method_def, scope)
|
|
1667
2067
|
return false unless unbounded_receiver_surface?(class_name, scope)
|
|
1668
2068
|
return true unless method_def.respond_to?(:defined_in)
|
|
@@ -1845,29 +2245,28 @@ module Rigor
|
|
|
1845
2245
|
# Slice 7 phase 11 — wrong-arity diagnostic. Fires when
|
|
1846
2246
|
# an explicit-receiver `Prism::CallNode` resolves to a
|
|
1847
2247
|
# method whose declared overloads do not admit the
|
|
1848
|
-
# supplied positional argument count
|
|
1849
|
-
#
|
|
1850
|
-
#
|
|
1851
|
-
#
|
|
1852
|
-
# arguments
|
|
1853
|
-
#
|
|
1854
|
-
#
|
|
1855
|
-
#
|
|
1856
|
-
#
|
|
1857
|
-
# disqualifies the rule.
|
|
2248
|
+
# supplied positional argument count (across all
|
|
2249
|
+
# overloads, see {#compute_arity_envelope}). One overload
|
|
2250
|
+
# with required keyword arguments or trailing positionals
|
|
2251
|
+
# leaves the whole method unchecked; calls with `*splat`, keyword, or
|
|
2252
|
+
# block-pass arguments are silently skipped to avoid
|
|
2253
|
+
# false positives. The check piggybacks on the same
|
|
2254
|
+
# scope-index lookup used by `undefined_method_diagnostic`;
|
|
2255
|
+
# it returns nil when the call's receiver / RBS coverage /
|
|
2256
|
+
# call shape disqualifies the rule.
|
|
1858
2257
|
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1859
|
-
def wrong_arity_diagnostic(path, call_node,
|
|
2258
|
+
def wrong_arity_diagnostic(path, call_node, facts, lexical_sites = nil)
|
|
1860
2259
|
return nil if call_node.receiver.nil?
|
|
1861
2260
|
return nil unless plain_positional_call?(call_node)
|
|
1862
2261
|
|
|
1863
|
-
scope =
|
|
2262
|
+
scope = facts.scope
|
|
1864
2263
|
return nil if scope.nil?
|
|
1865
2264
|
|
|
1866
2265
|
# ADR-67 WD6b — an inferred-parameter receiver's type is an open-call-site lower bound; a wrong-arity
|
|
1867
2266
|
# firing against it is an FP by construction. Decline.
|
|
1868
2267
|
return nil if inferred_param_receiver?(call_node, scope)
|
|
1869
2268
|
|
|
1870
|
-
receiver_type =
|
|
2269
|
+
receiver_type = facts.receiver_type
|
|
1871
2270
|
class_name = concrete_class_name(receiver_type)
|
|
1872
2271
|
return nil if class_name.nil?
|
|
1873
2272
|
|
|
@@ -1888,7 +2287,7 @@ module Rigor
|
|
|
1888
2287
|
# false positive. Skip arity-checking the chained position.
|
|
1889
2288
|
return nil if anonymous_struct_new_call?(call_node, class_name, kind)
|
|
1890
2289
|
|
|
1891
|
-
arity_envelope = arity_envelope_for(
|
|
2290
|
+
arity_envelope = arity_envelope_for(facts, class_name, kind)
|
|
1892
2291
|
return nil if arity_envelope.nil?
|
|
1893
2292
|
|
|
1894
2293
|
actual = (call_node.arguments&.arguments || []).size
|
|
@@ -1919,11 +2318,14 @@ module Rigor
|
|
|
1919
2318
|
# declaration. `trustworthy_signature` is the same lookup `argument_type_diagnostic` treats as
|
|
1920
2319
|
# authoritative over a source `def` (see its own comment beside that call), and the two rules must agree
|
|
1921
2320
|
# on whose contract binds, so a declared method is checked against its declaration.
|
|
1922
|
-
def arity_envelope_for(
|
|
2321
|
+
def arity_envelope_for(facts, class_name, kind)
|
|
2322
|
+
receiver_type = facts.receiver_type
|
|
2323
|
+
call_node = facts.call_node
|
|
2324
|
+
scope = facts.scope
|
|
1923
2325
|
if Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
1924
2326
|
return nil unless definition_available?(receiver_type, class_name, scope)
|
|
1925
2327
|
|
|
1926
|
-
method_def =
|
|
2328
|
+
method_def = receiver_signature(facts)
|
|
1927
2329
|
if method_def && !Rigor::RbsExtended.inferred_signature?(method_def)
|
|
1928
2330
|
return nil if undeclared_constructor?(class_name, call_node, kind, method_def)
|
|
1929
2331
|
|
|
@@ -2108,7 +2510,7 @@ module Rigor
|
|
|
2108
2510
|
# and union receivers where every member already
|
|
2109
2511
|
# disqualifies the call (avoid duplicating the
|
|
2110
2512
|
# undefined-method diagnostic).
|
|
2111
|
-
def nil_receiver_diagnostic(path, call_node,
|
|
2513
|
+
def nil_receiver_diagnostic(path, call_node, facts, lexical_sites = nil)
|
|
2112
2514
|
return nil if call_node.receiver.nil?
|
|
2113
2515
|
# Safe-navigation calls (`recv&.method`) already
|
|
2114
2516
|
# short-circuit on nil at runtime, so a nil-bearing
|
|
@@ -2122,7 +2524,7 @@ module Rigor
|
|
|
2122
2524
|
# produce false positives we cannot suppress.
|
|
2123
2525
|
return nil unless call_node.receiver.is_a?(Prism::LocalVariableReadNode)
|
|
2124
2526
|
|
|
2125
|
-
scope =
|
|
2527
|
+
scope = facts.scope
|
|
2126
2528
|
return nil if scope.nil?
|
|
2127
2529
|
|
|
2128
2530
|
# ADR-58 WD1 — a receiver whose `nil` constituent is purely
|
|
@@ -2148,7 +2550,7 @@ module Rigor
|
|
|
2148
2550
|
# construction. Decline.
|
|
2149
2551
|
return nil if scope.inferred_param?(call_node.receiver.name)
|
|
2150
2552
|
|
|
2151
|
-
receiver_type =
|
|
2553
|
+
receiver_type = facts.receiver_type
|
|
2152
2554
|
return nil unless receiver_type.is_a?(Type::Union)
|
|
2153
2555
|
|
|
2154
2556
|
# The rule only fires when the analyzer has access to
|
|
@@ -2157,7 +2559,7 @@ module Rigor
|
|
|
2157
2559
|
return nil unless Rigor::Reflection.rbs_class_known?("NilClass", scope: scope)
|
|
2158
2560
|
|
|
2159
2561
|
return nil unless nil_bearing_union_witnesses?(receiver_type, call_node.name, scope)
|
|
2160
|
-
return nil if nil_receiver_withheld?(call_node, receiver_type, scope, scope_index, lexical_sites)
|
|
2562
|
+
return nil if nil_receiver_withheld?(call_node, receiver_type, scope, facts.scope_index, lexical_sites)
|
|
2161
2563
|
|
|
2162
2564
|
build_nil_receiver_diagnostic(path, call_node)
|
|
2163
2565
|
end
|
|
@@ -2196,8 +2598,7 @@ module Rigor
|
|
|
2196
2598
|
# nil so the rule bails (silent). Every other receiver (including a
|
|
2197
2599
|
# `T | nil` union, which already has no single concrete class) flows
|
|
2198
2600
|
# through unchanged.
|
|
2199
|
-
def safe_navigation_receiver(call_node,
|
|
2200
|
-
receiver_type = scope.type_of(call_node.receiver)
|
|
2601
|
+
def safe_navigation_receiver(call_node, receiver_type)
|
|
2201
2602
|
return receiver_type unless call_node.safe_navigation?
|
|
2202
2603
|
return receiver_type unless nil_member?(receiver_type)
|
|
2203
2604
|
|
|
@@ -2519,22 +2920,24 @@ module Rigor
|
|
|
2519
2920
|
INTEGER_RAISING_OPERATORS = %i[/ % div modulo divmod].freeze
|
|
2520
2921
|
private_constant :INTEGER_RAISING_OPERATORS
|
|
2521
2922
|
|
|
2522
|
-
def always_raises_diagnostic(path, call_node,
|
|
2523
|
-
return nil unless integer_zero_division?(call_node,
|
|
2923
|
+
def always_raises_diagnostic(path, call_node, facts, lexical_sites = nil, raise_shield = nil)
|
|
2924
|
+
return nil unless integer_zero_division?(call_node, facts)
|
|
2524
2925
|
|
|
2525
2926
|
# ADR-121 WD7 — a refinement of `Integer`'s operator (or one Rigor cannot read) in effect may not raise.
|
|
2526
|
-
scope
|
|
2527
|
-
|
|
2927
|
+
return nil if refined_method_in_effect?("Integer", call_node, facts.scope, :instance, lexical_sites)
|
|
2928
|
+
|
|
2929
|
+
# Issue #3036 — a raise an enclosing `begin` / `def … rescue` catches flows to the handler.
|
|
2930
|
+
return nil if raise_shield&.rescued?(call_node, facts.scope)
|
|
2528
2931
|
|
|
2529
2932
|
build_always_raises_diagnostic(path, call_node)
|
|
2530
2933
|
end
|
|
2531
2934
|
|
|
2532
|
-
def integer_zero_division?(call_node,
|
|
2935
|
+
def integer_zero_division?(call_node, facts)
|
|
2533
2936
|
return false unless raising_call_shape?(call_node)
|
|
2534
2937
|
|
|
2535
|
-
scope =
|
|
2938
|
+
scope = facts.scope
|
|
2536
2939
|
return false if scope.nil?
|
|
2537
|
-
return false unless integer_rooted_for_diagnostic?(
|
|
2940
|
+
return false unless integer_rooted_for_diagnostic?(facts.receiver_type)
|
|
2538
2941
|
|
|
2539
2942
|
arg = single_argument(call_node)
|
|
2540
2943
|
arg && integer_zero_constant?(scope.type_of(arg))
|
|
@@ -2990,8 +3393,7 @@ module Rigor
|
|
|
2990
3393
|
# - Elsif chains (`subsequent` is itself an `IfNode`) ARE flagged — the entire downstream chain is
|
|
2991
3394
|
# unreachable when the outer predicate is a constant literal.
|
|
2992
3395
|
#
|
|
2993
|
-
#
|
|
2994
|
-
# mutation gaps named above are closed.
|
|
3396
|
+
# Inferred-constant predicates are the `flow.always-truthy-condition` rule's ({AlwaysTruthyConditionCollector}).
|
|
2995
3397
|
def unreachable_branch_diagnostic(path, node, scope_index)
|
|
2996
3398
|
scope = scope_index[node]
|
|
2997
3399
|
return nil if scope.nil?
|
|
@@ -3047,17 +3449,17 @@ module Rigor
|
|
|
3047
3449
|
# - Issue #1984 — Ruby hands a private call to `method_missing`, so a `method_missing` or
|
|
3048
3450
|
# `respond_to_missing?` a project entry on the receiver's chain records, on the side the call reaches,
|
|
3049
3451
|
# declines the rule ({#missing_hook_on_chain?}).
|
|
3050
|
-
def visibility_mismatch_diagnostic(path, call_node,
|
|
3452
|
+
def visibility_mismatch_diagnostic(path, call_node, facts, lexical_sites = nil)
|
|
3051
3453
|
return nil unless explicit_non_self_receiver?(call_node.receiver)
|
|
3052
3454
|
|
|
3053
|
-
scope =
|
|
3455
|
+
scope = facts.scope
|
|
3054
3456
|
return nil if scope.nil?
|
|
3055
3457
|
|
|
3056
3458
|
# ADR-67 WD6b — an inferred-parameter receiver's nominal type is an open-call-site lower bound; a
|
|
3057
3459
|
# private-method visibility firing against it is an FP by construction. Decline.
|
|
3058
3460
|
return nil if inferred_param_receiver?(call_node, scope)
|
|
3059
3461
|
|
|
3060
|
-
receiver_type =
|
|
3462
|
+
receiver_type = facts.receiver_type
|
|
3061
3463
|
case receiver_type
|
|
3062
3464
|
when Type::Nominal then instance_visibility_mismatch(path, call_node, scope, receiver_type, lexical_sites)
|
|
3063
3465
|
when Type::Singleton then singleton_visibility_mismatch(path, call_node, scope, receiver_type, lexical_sites)
|
|
@@ -3102,7 +3504,14 @@ module Rigor
|
|
|
3102
3504
|
build_visibility_mismatch_diagnostic(path, call_node, "singleton(#{class_name})")
|
|
3103
3505
|
end
|
|
3104
3506
|
|
|
3105
|
-
|
|
3507
|
+
# ADR-129 WD1 — a run-scoped Memo: one `[discovery, names]` slot keyed on the discovery index's identity, the
|
|
3508
|
+
# only input the name set reads.
|
|
3509
|
+
PRIVATE_NAMES_MEMO = TypingState.memo(
|
|
3510
|
+
:check_rules_private_singleton_names,
|
|
3511
|
+
run_scoped: true, audit: "private singleton names",
|
|
3512
|
+
reason: "one [discovery, names] slot keyed on the discovery index's identity; the name " \
|
|
3513
|
+
"set reads only its method-visibility tables"
|
|
3514
|
+
)
|
|
3106
3515
|
private_constant :PRIVATE_NAMES_MEMO
|
|
3107
3516
|
|
|
3108
3517
|
# Issue #1984 — the table-only gate the singleton side asks before it reads a chain, as the instance side asks
|
|
@@ -3113,12 +3522,23 @@ module Rigor
|
|
|
3113
3522
|
def private_singleton_candidate?(scope, name)
|
|
3114
3523
|
DependencyRecorder.read_visibility("*", name) if DependencyRecorder.active?
|
|
3115
3524
|
discovery = scope.discovery
|
|
3116
|
-
|
|
3525
|
+
if Inference::MemoAudit.enabled? && Inference::MemoAudit.suspended?
|
|
3526
|
+
return private_recorded_names(discovery.discovered_method_visibilities).include?(name.to_sym)
|
|
3527
|
+
end
|
|
3528
|
+
|
|
3529
|
+
state = TypingState.current
|
|
3530
|
+
slot = state[PRIVATE_NAMES_MEMO]
|
|
3117
3531
|
unless slot && slot[0].equal?(discovery)
|
|
3118
|
-
|
|
3119
|
-
|
|
3532
|
+
names = Inference::MemoAudit.key_audit("private singleton names") do
|
|
3533
|
+
private_recorded_names(discovery.discovered_method_visibilities)
|
|
3534
|
+
end
|
|
3535
|
+
slot = [discovery, names]
|
|
3536
|
+
state[PRIVATE_NAMES_MEMO] = slot
|
|
3537
|
+
return slot[1].include?(name.to_sym)
|
|
3120
3538
|
end
|
|
3121
|
-
slot[1]
|
|
3539
|
+
Inference::MemoAudit.check("private singleton names", slot[1], name) do
|
|
3540
|
+
private_recorded_names(discovery.discovered_method_visibilities)
|
|
3541
|
+
end.include?(name.to_sym)
|
|
3122
3542
|
end
|
|
3123
3543
|
|
|
3124
3544
|
def private_recorded_names(tables)
|
|
@@ -3381,12 +3801,9 @@ module Rigor
|
|
|
3381
3801
|
# - Receiver must be Nominal / Singleton / Constant
|
|
3382
3802
|
# (the same `concrete_class_name` test).
|
|
3383
3803
|
# - Method must be in RBS.
|
|
3384
|
-
# -
|
|
3385
|
-
#
|
|
3386
|
-
#
|
|
3387
|
-
# overload requires the dispatcher's full
|
|
3388
|
-
# acceptance plumbing.
|
|
3389
|
-
# - The selected overload must have NO
|
|
3804
|
+
# - Overloads: a single method type is checked exactly; with several, an argument is a mismatch only
|
|
3805
|
+
# when every overload rejects it ({#multi_overload_argument_mismatch}).
|
|
3806
|
+
# - The overload checked must have NO
|
|
3390
3807
|
# rest_positionals and NO trailing positionals —
|
|
3391
3808
|
# the two shapes under which argument N no longer
|
|
3392
3809
|
# binds to positional parameter N. Keyword
|
|
@@ -3430,15 +3847,15 @@ module Rigor
|
|
|
3430
3847
|
COERCE_DISPATCH_METHODS = %i[+ - * / % ** & | ^ << >> < > <= >=].to_set.freeze
|
|
3431
3848
|
private_constant :COERCE_DISPATCH_METHODS
|
|
3432
3849
|
|
|
3433
|
-
def argument_type_diagnostic(path, call_node,
|
|
3850
|
+
def argument_type_diagnostic(path, call_node, facts, lexical_sites = nil)
|
|
3434
3851
|
return nil if call_node.receiver.nil?
|
|
3435
3852
|
return nil if UNIVERSAL_EQUALITY_METHODS.include?(call_node.name)
|
|
3436
3853
|
return nil unless plain_positional_call?(call_node)
|
|
3437
3854
|
|
|
3438
|
-
scope =
|
|
3855
|
+
scope = facts.scope
|
|
3439
3856
|
return nil if scope.nil?
|
|
3440
3857
|
|
|
3441
|
-
receiver_type =
|
|
3858
|
+
receiver_type = facts.receiver_type
|
|
3442
3859
|
class_name = concrete_class_name(receiver_type)
|
|
3443
3860
|
return nil if class_name.nil?
|
|
3444
3861
|
|
|
@@ -3451,11 +3868,11 @@ module Rigor
|
|
|
3451
3868
|
return nil unless Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
3452
3869
|
return nil unless definition_available?(receiver_type, class_name, scope)
|
|
3453
3870
|
|
|
3454
|
-
method_def =
|
|
3871
|
+
method_def = receiver_signature(facts)
|
|
3455
3872
|
return nil if method_def.nil?
|
|
3456
3873
|
|
|
3457
3874
|
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def, environment: scope.environment)
|
|
3458
|
-
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, scope_index)
|
|
3875
|
+
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, facts.scope_index)
|
|
3459
3876
|
return nil if mismatch.nil?
|
|
3460
3877
|
return nil if argument_report_withheld?(receiver_type, class_name, call_node, mismatch, scope, lexical_sites)
|
|
3461
3878
|
|
|
@@ -3603,12 +4020,36 @@ module Rigor
|
|
|
3603
4020
|
# The non-nil channel (ADR-64 WD2/WD3): a single-concrete-class
|
|
3604
4021
|
# argument no overload admits, on a non-coerce method.
|
|
3605
4022
|
def non_nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
4023
|
+
return class_object_overload_mismatch(arg, arg_type, params, param_overrides, scope) if
|
|
4024
|
+
arg_type.is_a?(Type::Singleton)
|
|
3606
4025
|
return nil unless single_concrete_arg_class?(arg_type, scope)
|
|
3607
4026
|
return nil if params.any? { |param| param_accepts_arg_class?(param, arg_type, param_overrides, scope) }
|
|
3608
4027
|
|
|
3609
4028
|
{ node: arg, name: nil, expected: overload_param_expected_label(params), actual: arg_type }
|
|
3610
4029
|
end
|
|
3611
4030
|
|
|
4031
|
+
# Issue #2828 — the class-object twin of the non-nil channel. A `singleton(T)` overload the `resolv` overlay
|
|
4032
|
+
# prepends turned `getresources(d, String)`'s one-overload check (`expected singleton(Resolv::DNS::Query)`)
|
|
4033
|
+
# into this multi-overload one, which declined every class object. It fires only when every overload's
|
|
4034
|
+
# parameter at the position is a `singleton(...)` (a `singleton(T)` read through its class bound, see
|
|
4035
|
+
# `SingletonTypeParams.selection_param`) that provably refuses the argument's class; any other parameter
|
|
4036
|
+
# shape admits.
|
|
4037
|
+
def class_object_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
4038
|
+
return nil unless Rigor::Reflection.rbs_class_known?(arg_type.class_name, scope: scope)
|
|
4039
|
+
return nil unless params.all? do |param|
|
|
4040
|
+
!param_overrides[param.name] && param.type.is_a?(RBS::Types::ClassSingleton)
|
|
4041
|
+
end
|
|
4042
|
+
return nil unless params.all? { |param| class_singleton_refuses?(param.type, arg_type, scope) }
|
|
4043
|
+
|
|
4044
|
+
expected = params.map { |param| param.type.to_s.sub("singleton(::", "singleton(") }.uniq.join(" | ")
|
|
4045
|
+
{ node: arg, name: nil, expected: expected, actual: arg_type }
|
|
4046
|
+
end
|
|
4047
|
+
|
|
4048
|
+
def class_singleton_refuses?(rbs_type, arg_type, scope)
|
|
4049
|
+
translated = translate_param_type(rbs_type, scope.environment)
|
|
4050
|
+
translated.is_a?(Type::Singleton) && Inference::Acceptance.accepts(translated, arg_type, mode: :gradual).no?
|
|
4051
|
+
end
|
|
4052
|
+
|
|
3612
4053
|
# The matching positional RBS param across every overload, or nil when
|
|
3613
4054
|
# any overload has no param at `index` (arity divergence — the
|
|
3614
4055
|
# wrong-arity rule's concern, not this one's).
|
|
@@ -3629,6 +4070,7 @@ module Rigor
|
|
|
3629
4070
|
# Load-bearing since rbs 4.1 rewrote core signatures into this form
|
|
3630
4071
|
# (`Array#fetch`'s block overload is `[I < _ToInt, T] (I index) { … }`).
|
|
3631
4072
|
def resolve_param_bounds(param, method_type)
|
|
4073
|
+
param = Inference::MethodDispatcher::SingletonTypeParams.selection_param(param, method_type)
|
|
3632
4074
|
bounded = method_type.type_params.select(&:upper_bound)
|
|
3633
4075
|
return param if bounded.empty?
|
|
3634
4076
|
|
|
@@ -3915,6 +4357,7 @@ module Rigor
|
|
|
3915
4357
|
param_type = param_overrides[param.name] || translate_param_type(param.type, scope.environment)
|
|
3916
4358
|
return nil if param_type.is_a?(Type::Dynamic) || param_type.is_a?(Type::Top)
|
|
3917
4359
|
return nil if arg_type.is_a?(Type::Dynamic) || arg_type.is_a?(Type::Top)
|
|
4360
|
+
return nil if gradual_narrowing_accepted?(arg, arg_type, param_type, scope)
|
|
3918
4361
|
return nil unless argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
3919
4362
|
# Issue #1362 — a value that mixes such a global with something else (`c ? $stdout : StringIO.new`) is judged
|
|
3920
4363
|
# without the declared members the file never writes, as it was before the join.
|
|
@@ -3952,10 +4395,34 @@ module Rigor
|
|
|
3952
4395
|
Inference::Acceptance.accepts(param_type, type, mode: :gradual).no?
|
|
3953
4396
|
end
|
|
3954
4397
|
|
|
4398
|
+
# Issue #2656 — a local a class guard narrowed from a `Dynamic` value
|
|
4399
|
+
# ({DeclarationSourcedGuard.gradual_narrowed?}) is judged by its class, not by the literals the parameter
|
|
4400
|
+
# names: `mode(x) if x.is_a?(String)` with `mode: ("r" | "w")` is accepted, as the gradual value was, while
|
|
4401
|
+
# `num(x) if x.is_a?(String)` with `num: (1 | 2)` still reports, since no String is ever `1` or `2`. The
|
|
4402
|
+
# parameter's value-pinned members are widened to their classes ({Type::Combinator.widen_value_pinned}) and
|
|
4403
|
+
# the narrowed type is judged against that. The overload path ({#non_nil_arg_overload_mismatch}) already
|
|
4404
|
+
# judges by class, so it needs no such reading.
|
|
4405
|
+
#
|
|
4406
|
+
# A literal member of the narrowed type did not come from the gradual value (`x = h[:m] || "r"` narrows to
|
|
4407
|
+
# `"r" | String`), so it is judged against the parameter as written.
|
|
4408
|
+
def gradual_narrowing_accepted?(arg, arg_type, param_type, scope)
|
|
4409
|
+
return false unless DeclarationSourcedGuard.gradual_narrowed?(arg, scope)
|
|
4410
|
+
|
|
4411
|
+
members = arg_type.is_a?(Type::Union) ? arg_type.members : [arg_type]
|
|
4412
|
+
literals, rest = members.partition { |member| member.is_a?(Type::Constant) }
|
|
4413
|
+
if literals.any? { |member| Inference::Acceptance.accepts(param_type, member, mode: :gradual).no? }
|
|
4414
|
+
return false
|
|
4415
|
+
end
|
|
4416
|
+
return true if rest.empty?
|
|
4417
|
+
|
|
4418
|
+
widened = Type::Combinator.widen_value_pinned(param_type)
|
|
4419
|
+
!Inference::Acceptance.accepts(widened, Type::Combinator.union(*rest), mode: :gradual).no?
|
|
4420
|
+
end
|
|
4421
|
+
|
|
3955
4422
|
# The parameter rejects the argument AND the rejection is not a
|
|
3956
4423
|
# withheld declaration-sourced-nil case.
|
|
3957
4424
|
def argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
3958
|
-
return false unless
|
|
4425
|
+
return false unless rejects_beyond_class_objects?(param_type, arg_type)
|
|
3959
4426
|
|
|
3960
4427
|
# ADR-58 (N2 extension) — the same declaration-sourced-nil-is-not-
|
|
3961
4428
|
# diagnostic-fuel criterion that governs `possible-nil-receiver`
|
|
@@ -3971,6 +4438,31 @@ module Rigor
|
|
|
3971
4438
|
!declaration_sourced_nil_only_mismatch?(arg, arg_type, param_type, scope)
|
|
3972
4439
|
end
|
|
3973
4440
|
|
|
4441
|
+
# Issue #2855 — the parameter rejects the argument, and not only for its `Class` / `Module` members. An
|
|
4442
|
+
# unparameterised `Class` or `Module` nominal ({Acceptance.class_or_module_nominal?}) against a parameter
|
|
4443
|
+
# that admits class objects (`singleton(X)`, alone or in a union) is a class object whose identity inference
|
|
4444
|
+
# lost — a `const_get` result kept by `is_a?(Class)`, an `is_a?(Module)` guard — so it is withheld. The rest
|
|
4445
|
+
# of the argument is still judged: `Class | Integer` against `singleton(X)` reports for its `Integer`.
|
|
4446
|
+
# Acceptance itself keeps answering `no`, so overload selection is unchanged.
|
|
4447
|
+
def rejects_beyond_class_objects?(param_type, arg_type)
|
|
4448
|
+
return false unless Inference::Acceptance.accepts(param_type, arg_type, mode: :gradual).no?
|
|
4449
|
+
return true unless admits_class_objects?(param_type)
|
|
4450
|
+
|
|
4451
|
+
members = arg_type.is_a?(Type::Union) ? arg_type.members : [arg_type]
|
|
4452
|
+
rest = members.reject { |member| Inference::Acceptance.class_or_module_nominal?(member) }
|
|
4453
|
+
return true if rest.size == members.size
|
|
4454
|
+
|
|
4455
|
+
Inference::Acceptance.accepts(param_type, Type::Combinator.union(*rest), mode: :gradual).no?
|
|
4456
|
+
end
|
|
4457
|
+
|
|
4458
|
+
def admits_class_objects?(param_type)
|
|
4459
|
+
case param_type
|
|
4460
|
+
when Type::Singleton then true
|
|
4461
|
+
when Type::Union then param_type.members.any?(Type::Singleton)
|
|
4462
|
+
else false
|
|
4463
|
+
end
|
|
4464
|
+
end
|
|
4465
|
+
|
|
3974
4466
|
# True when `arg` is a declaration-sourced read whose rejection is
|
|
3975
4467
|
# caused solely by its nil constituent: stripping nil from the argument
|
|
3976
4468
|
# type yields a type the parameter accepts (gradual mode). Mirrors the
|
|
@@ -3986,7 +4478,12 @@ module Rigor
|
|
|
3986
4478
|
non_nil = Type::Combinator.union(*arg_type.members.reject { |m| nil_member?(m) })
|
|
3987
4479
|
return false if non_nil.is_a?(Type::Bot)
|
|
3988
4480
|
|
|
3989
|
-
Inference::Acceptance.accepts(param_type, non_nil, mode: :gradual)
|
|
4481
|
+
result = Inference::Acceptance.accepts(param_type, non_nil, mode: :gradual)
|
|
4482
|
+
# Issue #2855 — a `Class?` ivar against `singleton(X)` is withheld as its `Class` alone would be.
|
|
4483
|
+
# Issue #2863 — a `maybe` (a project class the host process has not loaded, so its ancestry is unresolved:
|
|
4484
|
+
# `singleton(Circle)` against `singleton(Shape)`) is not a rejection either: the non-nil part alone would not
|
|
4485
|
+
# fire, so the nil is the only reason and it is withheld.
|
|
4486
|
+
!result.no? || !rejects_beyond_class_objects?(param_type, non_nil)
|
|
3990
4487
|
end
|
|
3991
4488
|
|
|
3992
4489
|
# What this rule needs is that argument N binds to positional
|
|
@@ -4096,17 +4593,15 @@ module Rigor
|
|
|
4096
4593
|
# OWN declared return, and the sibling override rules gate on `defined_on?` — this one did not.
|
|
4097
4594
|
# The inherited-declaration signal belongs to the ADR-35 family, which requires both sides
|
|
4098
4595
|
# authored. Decided in ADR-110 WD3.
|
|
4099
|
-
# - Skips methods whose enclosing
|
|
4100
|
-
# (top-level / module-level methods land outside the rule).
|
|
4596
|
+
# - Skips methods whose enclosing `self` names no class (top-level methods land outside the rule).
|
|
4101
4597
|
# - Skips methods whose body's last expression is absent or types as `Dynamic[top]` (the
|
|
4102
4598
|
# analyzer's fail-soft fallback) — emitting on `Dynamic[top]` would be noise.
|
|
4103
4599
|
# - Compares the inferred body type against the declared return via `accepts?`:
|
|
4104
4600
|
# :yes → silent
|
|
4105
4601
|
# :no → emit at :error (severity_profile may re-stamp; default `balanced` keeps the
|
|
4106
4602
|
# authored severity).
|
|
4107
|
-
# :maybe →
|
|
4108
|
-
|
|
4109
|
-
def return_type_mismatch_diagnostic(path, def_node, scope_index)
|
|
4603
|
+
# :maybe → silent (see {#compare_return}; ADR-8's `strict` promotion is deferred).
|
|
4604
|
+
def return_type_mismatch_diagnostic(path, def_node, scope_index, replaced_defs = nil)
|
|
4110
4605
|
return nil if def_node.body.nil?
|
|
4111
4606
|
|
|
4112
4607
|
last_expr = body_last_expression(def_node.body)
|
|
@@ -4115,7 +4610,7 @@ module Rigor
|
|
|
4115
4610
|
inner_scope = scope_index[last_expr] || scope_index[def_node.body] || scope_index[def_node]
|
|
4116
4611
|
return nil if inner_scope.nil?
|
|
4117
4612
|
|
|
4118
|
-
declared = declared_return_type(def_node, scope_index)
|
|
4613
|
+
declared = declared_return_type(def_node, scope_index, replaced_defs)
|
|
4119
4614
|
return nil if declared.nil?
|
|
4120
4615
|
|
|
4121
4616
|
# Issue #1362 — a body that ends on a builtin global still on its declared seed, or on a local copied from
|
|
@@ -4162,14 +4657,18 @@ module Rigor
|
|
|
4162
4657
|
# `non-empty-array[Integer]`, etc. — are stricter than the underlying RBS class, so a body whose
|
|
4163
4658
|
# inferred type the bare RBS sig would accept may still fail the refinement (e.g. `def name; "";
|
|
4164
4659
|
# end` returns `Constant[""]`, accepted by `String` but rejected by `non-empty-string`).
|
|
4165
|
-
def declared_return_type(def_node, scope_index)
|
|
4660
|
+
def declared_return_type(def_node, scope_index, replaced_defs = nil)
|
|
4166
4661
|
scope = scope_index[def_node]
|
|
4167
4662
|
return nil if scope.nil?
|
|
4168
4663
|
|
|
4169
4664
|
self_type = scope.self_type
|
|
4170
4665
|
return nil unless self_type.respond_to?(:class_name)
|
|
4171
4666
|
|
|
4172
|
-
side = def_node.receiver.nil?
|
|
4667
|
+
side = if def_node.receiver.nil?
|
|
4668
|
+
receiverless_def_side(scope, self_type.class_name, def_node)
|
|
4669
|
+
else
|
|
4670
|
+
receiver_def_side(scope, self_type.class_name, def_node)
|
|
4671
|
+
end
|
|
4173
4672
|
return nil if side.nil?
|
|
4174
4673
|
|
|
4175
4674
|
method_def =
|
|
@@ -4180,6 +4679,7 @@ module Rigor
|
|
|
4180
4679
|
end
|
|
4181
4680
|
return nil if method_def.nil?
|
|
4182
4681
|
return nil unless defined_on?(method_def, self_type.class_name)
|
|
4682
|
+
return nil if replaced_receiver_def?(def_node, replaced_defs)
|
|
4183
4683
|
|
|
4184
4684
|
override = Rigor::RbsExtended.read_return_type_override(method_def, environment: scope.environment)
|
|
4185
4685
|
return override if override
|
|
@@ -4202,6 +4702,30 @@ module Rigor
|
|
|
4202
4702
|
nil
|
|
4203
4703
|
end
|
|
4204
4704
|
|
|
4705
|
+
# Issue #3155 — a singleton def a later certain def replaced ({ReplacedSingletonDefs}). Asked only once the def
|
|
4706
|
+
# has a signature to answer to, so the file walk runs only then.
|
|
4707
|
+
def replaced_receiver_def?(def_node, replaced_defs)
|
|
4708
|
+
!def_node.receiver.nil? && replaced_defs&.replaced?(def_node)
|
|
4709
|
+
end
|
|
4710
|
+
|
|
4711
|
+
# Issue #3155 — `def self.x`, or `def K.x` in `K`'s body, answers to K's singleton signature. A def on another
|
|
4712
|
+
# object (`def String.q`, `def obj.q` in a class body) or `def self.x` in a `class << self` body (a method of
|
|
4713
|
+
# the singleton class's own singleton) answers to none of K's: `nil`, and the caller declines.
|
|
4714
|
+
# {#declared_return_type} also declines, once a signature is found, a singleton def a later certain def of the
|
|
4715
|
+
# name in the same body replaced (`def K.w`; `def self.w`; {ReplacedSingletonDefs}). `def (self).x` reads as
|
|
4716
|
+
# `def self.x`; `K = self; def K.x` is not read.
|
|
4717
|
+
def receiver_def_side(scope, class_name, def_node)
|
|
4718
|
+
receiver = ReplacedSingletonDefs.unparenthesized(def_node.receiver)
|
|
4719
|
+
lexical_self = if receiver.is_a?(Prism::SelfNode)
|
|
4720
|
+
!scope.singleton_class_body?
|
|
4721
|
+
else
|
|
4722
|
+
Inference::ScopeIndexer.def_receiver_targets_lexical_self?(receiver, class_name.split("::"))
|
|
4723
|
+
end
|
|
4724
|
+
return nil unless lexical_self
|
|
4725
|
+
|
|
4726
|
+
:singleton
|
|
4727
|
+
end
|
|
4728
|
+
|
|
4205
4729
|
# The tables hold the live node or one re-resolved from a `DefHandle`, so identity is the name and span.
|
|
4206
4730
|
def same_def_node?(recorded, def_node)
|
|
4207
4731
|
recorded.is_a?(Prism::DefNode) && recorded.name == def_node.name &&
|
|
@@ -4241,9 +4765,9 @@ module Rigor
|
|
|
4241
4765
|
# precision improvements.
|
|
4242
4766
|
def compare_return(declared, inferred)
|
|
4243
4767
|
result = declared.accepts(inferred)
|
|
4244
|
-
return
|
|
4768
|
+
return nil unless result.no?
|
|
4245
4769
|
|
|
4246
|
-
|
|
4770
|
+
:error
|
|
4247
4771
|
end
|
|
4248
4772
|
|
|
4249
4773
|
def build_return_type_mismatch_diagnostic(path, def_node, declared, inferred, severity)
|
|
@@ -4345,8 +4869,10 @@ module Rigor
|
|
|
4345
4869
|
# defined LATER under that name would change the answer; `DefinerResolution` files the negative class
|
|
4346
4870
|
# edge of each external entry it tests.
|
|
4347
4871
|
def overridden_ancestors_start(scope, class_name)
|
|
4348
|
-
|
|
4349
|
-
|
|
4872
|
+
# Issue #2979 — past the entries of the names bound to the same class (`K = Struct.new("Named", …)`):
|
|
4873
|
+
# a `def` there redefines the class's method rather than overriding an ancestor's.
|
|
4874
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :methods)
|
|
4875
|
+
chain.ancestors_start(scope, class_name.to_s)
|
|
4350
4876
|
end
|
|
4351
4877
|
|
|
4352
4878
|
# `[defining_class, visibility]` for the nearest user-source ancestor that defines an instance
|
|
@@ -4441,6 +4967,7 @@ module Rigor
|
|
|
4441
4967
|
parent_return = declared_return_union(parent_method, scope.environment, type_vars: parent_type_vars)
|
|
4442
4968
|
return nil if override_return.nil? || parent_return.nil?
|
|
4443
4969
|
return nil if dynamic_top?(parent_return) # untyped / unbound-generic parent contract
|
|
4970
|
+
return nil if void_over_nil?(override_method, parent_return)
|
|
4444
4971
|
|
|
4445
4972
|
return nil unless parent_return.accepts(override_return).no?
|
|
4446
4973
|
|
|
@@ -4449,22 +4976,42 @@ module Rigor
|
|
|
4449
4976
|
)
|
|
4450
4977
|
end
|
|
4451
4978
|
|
|
4979
|
+
# A `-> void` override of a parent return of exactly `nil` (or `NilClass`) is exempt (#3046): both
|
|
4980
|
+
# say "no useful value", and callers cannot use a void return. Keyed on the declared `void` of every
|
|
4981
|
+
# arm (the translator reads `void` as top, so a `-> top` override still fires); any non-nil parent
|
|
4982
|
+
# return still fires.
|
|
4983
|
+
def void_over_nil?(override_method, parent_return)
|
|
4984
|
+
return false unless nil_type?(parent_return)
|
|
4985
|
+
|
|
4986
|
+
override_method.method_types.all? { |mt| mt.type.return_type.is_a?(::RBS::Types::Bases::Void) }
|
|
4987
|
+
end
|
|
4988
|
+
|
|
4989
|
+
# `nil`, or the nominal `NilClass`, whose only value it is.
|
|
4990
|
+
def nil_type?(type)
|
|
4991
|
+
return type.value.nil? if type.is_a?(Type::Constant)
|
|
4992
|
+
|
|
4993
|
+
type.is_a?(Type::Nominal) && type.class_name == "NilClass" && type.type_args.empty?
|
|
4994
|
+
end
|
|
4995
|
+
|
|
4452
4996
|
# `[defining_class, RBS::Definition::Method]` for the nearest project-discovered ancestor whose
|
|
4453
4997
|
# RBS declares `method_name` (not the starting class's own declaration), or nil.
|
|
4454
4998
|
def nearest_ancestor_method_def(scope, class_name, method_name)
|
|
4455
4999
|
from = overridden_ancestors_start(scope, class_name)
|
|
4456
5000
|
return nil if from.nil?
|
|
4457
5001
|
|
|
5002
|
+
passed_owners = []
|
|
4458
5003
|
case Inference::DefinerResolution.resolve(scope, class_name, method_name, :instance,
|
|
4459
5004
|
question: :override, from: from) do |ancestor|
|
|
4460
|
-
|
|
4461
|
-
# The signature text is the answer two worlds compare: an `RBS::Definition::Method` has no
|
|
4462
|
-
# structural equality of its own.
|
|
4463
|
-
method_def.method_types.map(&:to_s) if method_def && !defined_on?(method_def, class_name)
|
|
5005
|
+
override_parent_answer(scope, ancestor, method_name, passed_owners)
|
|
4464
5006
|
end
|
|
4465
|
-
in Inference::DefinerResolution::Known(owner:)
|
|
4466
|
-
|
|
4467
|
-
|
|
5007
|
+
in Inference::DefinerResolution::Known(owner:, answer:)
|
|
5008
|
+
# The nearest declaration must be the one that answers: an ancestor asked past for a project
|
|
5009
|
+
# owner the walk never reached (one the Ruby chain does not hold) leaves the rule silent.
|
|
5010
|
+
unless answer.last == RBS_ONLY_OWNER ||
|
|
5011
|
+
passed_owners.any? { |passed| passed != normalize_class_name(owner) }
|
|
5012
|
+
method_def = safe_instance_method_definition(owner, method_name, scope)
|
|
5013
|
+
method_def && [owner, method_def]
|
|
5014
|
+
end
|
|
4468
5015
|
in Inference::DefinerResolution::UNKNOWN # the case/in contract needs one arm per answer
|
|
4469
5016
|
nil
|
|
4470
5017
|
in Inference::DefinerResolution::ABSENT # rubocop:disable Lint/DuplicateBranch -- see above
|
|
@@ -4472,6 +5019,38 @@ module Rigor
|
|
|
4472
5019
|
end
|
|
4473
5020
|
end
|
|
4474
5021
|
|
|
5022
|
+
# The answer an RBS-only owner gives the override walk: it ends the walk, and the rule stays silent.
|
|
5023
|
+
RBS_ONLY_OWNER = :rbs_only_owner
|
|
5024
|
+
private_constant :RBS_ONLY_OWNER
|
|
5025
|
+
|
|
5026
|
+
# One project ancestor's answer to the override walk. The signature text is the answer two worlds
|
|
5027
|
+
# compare: an `RBS::Definition::Method` has no structural equality of its own. Only an ancestor whose
|
|
5028
|
+
# own RBS declares the method answers with it (#3046). One that inherits the declaration from another
|
|
5029
|
+
# project class or module is asked past, so that owner answers at its own entry; the owner is recorded
|
|
5030
|
+
# in `passed_owners`, which the answering owner must match. One that inherits it from an RBS-only owner
|
|
5031
|
+
# the project chain cannot see ends the walk silent ({RBS_ONLY_OWNER}): that owner is the nearest
|
|
5032
|
+
# declaration, and RBS-only ancestors are #130's.
|
|
5033
|
+
def override_parent_answer(scope, ancestor, method_name, passed_owners)
|
|
5034
|
+
method_def = safe_instance_method_definition(ancestor, method_name, scope)
|
|
5035
|
+
return nil if method_def.nil?
|
|
5036
|
+
return method_def.method_types.map(&:to_s) if declared_by?(method_def, ancestor)
|
|
5037
|
+
|
|
5038
|
+
owner = method_def.implemented_in || method_def.defined_in
|
|
5039
|
+
return RBS_ONLY_OWNER unless owner && scope.discovered_classes.key?(normalize_class_name(owner))
|
|
5040
|
+
|
|
5041
|
+
passed_owners << normalize_class_name(owner)
|
|
5042
|
+
nil
|
|
5043
|
+
end
|
|
5044
|
+
|
|
5045
|
+
# True when `ancestor`'s own RBS declares `method_def`: on its body, or through an interface it
|
|
5046
|
+
# includes itself (RBS records the including class as `implemented_in`).
|
|
5047
|
+
def declared_by?(method_def, ancestor)
|
|
5048
|
+
return true if defined_on?(method_def, ancestor)
|
|
5049
|
+
return false unless method_def.defined_in&.interface?
|
|
5050
|
+
|
|
5051
|
+
normalize_class_name(method_def.implemented_in) == normalize_class_name(ancestor)
|
|
5052
|
+
end
|
|
5053
|
+
|
|
4475
5054
|
def safe_instance_method_definition(class_name, method_name, scope)
|
|
4476
5055
|
Reflection.instance_method_definition(class_name, method_name, scope: scope)
|
|
4477
5056
|
rescue StandardError
|