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
|
@@ -9,6 +9,8 @@ require_relative "../type"
|
|
|
9
9
|
require_relative "guard_rebinding"
|
|
10
10
|
require_relative "project_method_ownership"
|
|
11
11
|
require_relative "refined_dispatch/own_method"
|
|
12
|
+
require_relative "../typing_state"
|
|
13
|
+
require_relative "../memo_audit"
|
|
12
14
|
|
|
13
15
|
module Rigor
|
|
14
16
|
module Inference
|
|
@@ -85,7 +87,9 @@ module Rigor
|
|
|
85
87
|
:to_r, :to_c, :to_proc, :to_path, :to_sym, :to_io, :coerce, :exception, :divmod, :each, :respond_to?,
|
|
86
88
|
:respond_to_missing?, :method_missing, :initialize_copy, :marshal_dump, :pretty_print
|
|
87
89
|
].freeze
|
|
88
|
-
|
|
90
|
+
# Issue #2203 — the classes and module every project class's instances reach past its own chain.
|
|
91
|
+
IMPLICIT_ROOTS = %w[Object Kernel BasicObject].freeze
|
|
92
|
+
private_constant :UNPROVABLE_NODES, :LITERAL_CLASSES, :SELF_SAFE_NAMES, :PROTOCOL_NAMES, :IMPLICIT_ROOTS
|
|
89
93
|
|
|
90
94
|
# Raised inside a proof to abandon it.
|
|
91
95
|
class Unprovable < StandardError
|
|
@@ -113,6 +117,50 @@ module Rigor
|
|
|
113
117
|
nil
|
|
114
118
|
end
|
|
115
119
|
|
|
120
|
+
# ADR-129 WD1 — a run-scoped Memo: the `[deferred_ranges, counts]` pair is one table and the counts computed
|
|
121
|
+
# from it alone, so the counts are a function of the table identity the pair is keyed on.
|
|
122
|
+
DEFINITION_COUNTS = TypingState.memo(
|
|
123
|
+
:self_call_ivar_writes_definition_counts,
|
|
124
|
+
run_scoped: true, audit: "self-call ivar definition counts",
|
|
125
|
+
reason: "the instance-def counts of one deferred-ranges table; keyed on its identity"
|
|
126
|
+
)
|
|
127
|
+
private_constant :DEFINITION_COUNTS
|
|
128
|
+
|
|
129
|
+
# `{[owner, name] => the number of instance `def` rows of name the project records for owner}`, over every
|
|
130
|
+
# file of `deferred_ranges` (a `discovered_deferred_ranges` table). Built once per table and kept in ONE
|
|
131
|
+
# thread slot keyed by the table's identity (#2300): the table is never written in place, a file whose rows
|
|
132
|
+
# equal the seed's keeps the seed's table, and a scan of every file's rows per query was the cost once the
|
|
133
|
+
# table held the whole project. The slot holds one table and its counts, the next table replaces them, and the
|
|
134
|
+
# run that filled it releases it when it ends (run-scoped, #2399).
|
|
135
|
+
def definition_counts(deferred_ranges)
|
|
136
|
+
return count_definitions(deferred_ranges) if MemoAudit.enabled? && MemoAudit.suspended?
|
|
137
|
+
|
|
138
|
+
state = TypingState.current
|
|
139
|
+
slot = state[DEFINITION_COUNTS]
|
|
140
|
+
if slot && slot[0].equal?(deferred_ranges)
|
|
141
|
+
return MemoAudit.check("self-call ivar definition counts", slot[1], deferred_ranges.size) do
|
|
142
|
+
count_definitions(deferred_ranges)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
counts = MemoAudit.key_audit("self-call ivar definition counts") { count_definitions(deferred_ranges) }
|
|
147
|
+
state[DEFINITION_COUNTS] = [deferred_ranges, counts]
|
|
148
|
+
counts
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def count_definitions(deferred_ranges)
|
|
152
|
+
counts = {}
|
|
153
|
+
deferred_ranges.each_value do |rows|
|
|
154
|
+
rows.each do |row|
|
|
155
|
+
next unless row[3] == :instance
|
|
156
|
+
|
|
157
|
+
key = [row[4], row[2]]
|
|
158
|
+
counts[key] = counts.fetch(key, 0) + 1
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
counts
|
|
162
|
+
end
|
|
163
|
+
|
|
116
164
|
# One proof's state: the instance variables read code writes, and the `def` bodies already read.
|
|
117
165
|
class Proof
|
|
118
166
|
attr_reader :written
|
|
@@ -128,7 +176,7 @@ module Rigor
|
|
|
128
176
|
# Reads the method an implicit-self or `self.` call to `call_node`'s name runs.
|
|
129
177
|
def self_call(call_node)
|
|
130
178
|
name = call_node.name
|
|
131
|
-
unprovable! if GuardRebinding.code_running_name?(name) || refined?(name)
|
|
179
|
+
unprovable! if GuardRebinding.code_running_name?(name) || refined?(name, on_self: true)
|
|
132
180
|
|
|
133
181
|
if ProjectMethodOwnership.defines?(@class_name, name, :instance, @scope)
|
|
134
182
|
read_def(proven_def(name))
|
|
@@ -185,19 +233,66 @@ module Rigor
|
|
|
185
233
|
|
|
186
234
|
private
|
|
187
235
|
|
|
188
|
-
# Whether a `refine` block
|
|
189
|
-
# read code sits may route the call to it (`refine Kernel do def warn …`)
|
|
236
|
+
# Whether a `refine` block in the project defines `name` where the call may reach it: a `using` in effect
|
|
237
|
+
# where the read code sits may route the call to it (`refine Kernel do def warn …`).
|
|
190
238
|
#
|
|
191
239
|
# Issue #2163 — the answer reads every file's refinements whole, and a refined class off the chain (`Kernel`,
|
|
192
240
|
# `IO`, `String`) is no `mark:` the chain read files, so it records `refinement:<name>` and `refinement:*`
|
|
193
241
|
# whichever way it answers, as `InEffectRefinements` consumers do: a file that starts or stops refining the
|
|
194
242
|
# name re-checks the reader.
|
|
195
|
-
|
|
243
|
+
#
|
|
244
|
+
# Issue #2203 — for a call on `self` (`on_self:`) whose reach {#self_reach} knows, a row of the name counts only
|
|
245
|
+
# on a class key in that reach, on the wildcard class key, or on a key the walk could not name
|
|
246
|
+
# (`InEffectRefinements.class_wildcard_key?`); a names-wildcard row (a computed `define_method`,
|
|
247
|
+
# `import_methods`) counts as the name, but only on a class key in the reach. A refinement of `String` leaves
|
|
248
|
+
# a `Holder` alone: dropping the proof is no safer, since the intervening-call reset restores the class-ivar
|
|
249
|
+
# seed and a class guard's narrowing then reports. Where the reach is unknown, and for a call on another
|
|
250
|
+
# receiver, a row of the name counts on any class and a names-wildcard row never does, as before the issue.
|
|
251
|
+
def refined?(name, on_self: false)
|
|
252
|
+
wildcard = Scope::DiscoveryIndex::REFINEMENT_WILDCARD
|
|
196
253
|
if Analysis::DependencyRecorder.active?
|
|
197
254
|
Analysis::DependencyRecorder.read_name(:refinement, name)
|
|
198
|
-
Analysis::DependencyRecorder.read_name(:refinement,
|
|
255
|
+
Analysis::DependencyRecorder.read_name(:refinement, wildcard)
|
|
199
256
|
end
|
|
200
|
-
|
|
257
|
+
reach = on_self ? self_reach : nil
|
|
258
|
+
@scope.discovered_refinements.any? do |refined, methods|
|
|
259
|
+
next methods.key?(name) if reach.nil?
|
|
260
|
+
|
|
261
|
+
if reach.include?(refined)
|
|
262
|
+
methods.key?(name) || methods.key?(wildcard)
|
|
263
|
+
else
|
|
264
|
+
methods.key?(name) && InEffectRefinements.class_wildcard_key?(@scope, refined)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Issue #2203 — the classes and modules on the instance chain of `self`'s class and of `Object`, `Kernel` and
|
|
270
|
+
# `BasicObject` (which holds the modules the project mixes into them), with `Object`'s RBS ancestors
|
|
271
|
+
# (`PP::ObjectMixin`); nil when a chain is cut, mixes in a module the tables cannot name, ends in a superclass
|
|
272
|
+
# they do not know (`class Holder < Struct.new(:a)`), or holds an external entry. A project subclass's chain
|
|
273
|
+
# needs no read: {#no_protocol_override} gives the proof up on a refined class or module anywhere on it. The
|
|
274
|
+
# implicit roots' chains are recorded, so a file that mixes a module into `Object` re-checks the reader;
|
|
275
|
+
# `self`'s own chain is recorded by {#no_protocol_override}'s read.
|
|
276
|
+
def self_reach
|
|
277
|
+
return @self_reach if defined?(@self_reach)
|
|
278
|
+
|
|
279
|
+
@self_reach = read_self_reach
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def read_self_reach
|
|
283
|
+
names = Set.new
|
|
284
|
+
[@class_name.to_s, *IMPLICIT_ROOTS].each do |class_name|
|
|
285
|
+
chain = Scope::ResolutionChain.for(@scope, class_name, :instance, :methods)
|
|
286
|
+
chain.record(@scope) if Analysis::DependencyRecorder.active?
|
|
287
|
+
return nil if chain.truncated? || chain.wildcard_mixin? || chain.opaque_tail?(@scope)
|
|
288
|
+
return nil if chain.entries.any?(&:external?)
|
|
289
|
+
|
|
290
|
+
chain.entries.each { |entry| names << entry.name }
|
|
291
|
+
end
|
|
292
|
+
loader = @scope.environment&.rbs_loader
|
|
293
|
+
return nil if loader.nil?
|
|
294
|
+
|
|
295
|
+
names.merge(loader.ancestor_names_for("Object"))
|
|
201
296
|
end
|
|
202
297
|
|
|
203
298
|
def call_receiver(node) = node.is_a?(Prism::CallNode) ? node.receiver : nil
|
|
@@ -268,11 +363,7 @@ module Rigor
|
|
|
268
363
|
end
|
|
269
364
|
|
|
270
365
|
def definition_count(owner, name)
|
|
271
|
-
|
|
272
|
-
@scope.discovered_deferred_ranges.each_value do |rows|
|
|
273
|
-
count += rows.count { |row| row[2] == name && row[3] == :instance && row[4] == owner }
|
|
274
|
-
end
|
|
275
|
-
count
|
|
366
|
+
SelfCallIvarWrites.definition_counts(@scope.discovered_deferred_ranges).fetch([owner, name], 0)
|
|
276
367
|
end
|
|
277
368
|
|
|
278
369
|
# Whether `chain`'s own order stands with no second world: no order mark and no fork. `ResolutionChain#settle`
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "closure_escape_analyzer"
|
|
6
|
+
require_relative "project_method_ownership"
|
|
7
|
+
require_relative "refined_dispatch"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
module Inference
|
|
11
|
+
# Issue #2861 — whether a block-carrying call provably runs its literal block under the CALLER's `self`, so the
|
|
12
|
+
# block body is no more opaque than the scope around it (`Scope#entering_self_preserving_block`).
|
|
13
|
+
#
|
|
14
|
+
# Issue #316 enters every block with `self` unmodelled, because a DSL call (`RSpec.describe`, `Class.new`, a
|
|
15
|
+
# project `def run(&b) = instance_exec(&b)`) may run the block on another object, and the call cannot be told
|
|
16
|
+
# apart from `Array#each` without knowing the callee. Here the callee is known: the explicit receiver's type and
|
|
17
|
+
# method are one {ClosureEscapeAnalyzer} classifies as `:non_escaping` — a catalogued core iterator, `tap` /
|
|
18
|
+
# `then`, or a project class's inherited `Enumerable` method — each of which `yield`s, which runs the block
|
|
19
|
+
# with its own `self`. The project must not define the method anywhere the receiver looks it up
|
|
20
|
+
# ({ProjectMethodOwnership.defines?}): a project reopening of `Array#each` could `instance_exec` the block.
|
|
21
|
+
# A `def` inside `refine X do … end` is no such answer, so where the call site's in-effect refinements may
|
|
22
|
+
# define the name the call declines too (issue #2909) — a refined `Integer#times` or `Object#tap` could
|
|
23
|
+
# `instance_exec` the block just the same.
|
|
24
|
+
#
|
|
25
|
+
# The project must not define it on `Object`, `Kernel` or another owner every receiver reaches either
|
|
26
|
+
# ({.universally_defined?}). A receiverless call is the DSL shape and never answers true, nor does a receiver Rigor
|
|
27
|
+
# cannot see. A `self.` receiver is read like any other explicit one, through its type.
|
|
28
|
+
module SelfPreservingBlock
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# `classification` is the receiver's {ClosureEscapeAnalyzer.classify} answer when the caller already holds it.
|
|
32
|
+
def call?(call_node, receiver_type, scope, classification: nil)
|
|
33
|
+
return false unless call_node.is_a?(Prism::CallNode) && call_node.block.is_a?(Prism::BlockNode)
|
|
34
|
+
|
|
35
|
+
return false if call_node.receiver.nil? || receiver_type.nil?
|
|
36
|
+
|
|
37
|
+
method_name = call_node.name
|
|
38
|
+
classification ||= ClosureEscapeAnalyzer.classify(receiver_type: receiver_type, method_name: method_name,
|
|
39
|
+
scope: scope)
|
|
40
|
+
return false unless classification == :non_escaping
|
|
41
|
+
|
|
42
|
+
project_shadow_free?(call_node, receiver_type, method_name, scope)
|
|
43
|
+
rescue StandardError
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The project-side half of {.call?}: nothing the project defines — a plain `def` on a lookup target or a
|
|
48
|
+
# {UNIVERSAL_OWNERS} entry, nor a refinement in effect at the call site — may answer the yielding method
|
|
49
|
+
# instead of the catalogued non-escaping one.
|
|
50
|
+
def project_shadow_free?(call_node, receiver_type, method_name, scope)
|
|
51
|
+
targets = ProjectMethodOwnership.targets(receiver_type)
|
|
52
|
+
return false if targets.nil? || targets.empty?
|
|
53
|
+
return false if universally_defined?(method_name, scope)
|
|
54
|
+
|
|
55
|
+
targets.none? { |class_name, kind| ProjectMethodOwnership.defines?(class_name, method_name, kind, scope) } &&
|
|
56
|
+
!refined_in_effect?(call_node, method_name, scope)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The classes every receiver's lookup reaches: an instance of any class is an `Object` (a class object is also
|
|
60
|
+
# a `Module` and a `Class`), so a project method of the name on one of them, or on `Kernel`, which
|
|
61
|
+
# {ProjectMethodOwnership.defines?} reaches through `Object`'s ancestry, may be the one the call runs, whatever
|
|
62
|
+
# class the receiver names. `ClosureEscapeAnalyzer` answers `tap` / `then` / `yield_self` for every class
|
|
63
|
+
# without asking who defines them.
|
|
64
|
+
UNIVERSAL_OWNERS = %w[Object Module Class].freeze
|
|
65
|
+
private_constant :UNIVERSAL_OWNERS
|
|
66
|
+
|
|
67
|
+
# Whether the project defines `method_name` on a {UNIVERSAL_OWNERS} entry. A top-level `def` of the name is
|
|
68
|
+
# private, so an explicit-receiver call raises NoMethodError before any block runs, and is not counted.
|
|
69
|
+
def universally_defined?(method_name, scope)
|
|
70
|
+
UNIVERSAL_OWNERS.any? { |owner| ProjectMethodOwnership.defines?(owner, method_name, :instance, scope) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Issue #2909 — whether a refinement in effect at `call_node` may define `method_name`. The refined-class
|
|
74
|
+
# walk is not consulted: any in-effect row for the name declines, the direction ADR-5 prefers, and it covers
|
|
75
|
+
# `refine Object` / `refine Kernel` without a universal-owner path of its own. An empty list — no `using`
|
|
76
|
+
# reaches the node — is the common case and costs the one lookup. The answer reads the refinement table by
|
|
77
|
+
# method name, so `refinement:<name>` is recorded as {ExpressionTyper}'s refined dispatch records it.
|
|
78
|
+
def refined_in_effect?(call_node, method_name, scope)
|
|
79
|
+
list = scope.in_effect_refinements(call_node)
|
|
80
|
+
return false if list.empty?
|
|
81
|
+
|
|
82
|
+
InEffectRefinements.record_refinement_names(method_name)
|
|
83
|
+
RefinedDispatch.refined_anywhere?(scope, method_name, list)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
require_relative "content_join"
|
|
5
|
+
require_relative "mutation_rejoin"
|
|
6
|
+
require_relative "rewrite_mutation"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
# The Set half of {MutationWidening}'s seams (issue #2294), beside {StringMutation}.
|
|
11
|
+
#
|
|
12
|
+
# `f = Set["a"]; f.clear; f << 1; f.map { |v| v.fdiv(2) }` reported `undefined method 'fdiv' for String`
|
|
13
|
+
# on code that prints `[0.5]`: the mutation seams answered `Tuple`, `HashShape`, `Constant[String]` and the
|
|
14
|
+
# re-openable `Array` / `Hash` nominals, so a `Set` kept the elements it was built with through every
|
|
15
|
+
# `<<`, `add`, `merge` and `replace`.
|
|
16
|
+
#
|
|
17
|
+
# A Set has no slots and no arity, so the only thing a mutation falsifies is the element set. Both carriers
|
|
18
|
+
# this module answers end at the typed Set #2290 builds for a non-constant literal — `Set[E | Dynamic[top]]`:
|
|
19
|
+
#
|
|
20
|
+
# - a constant-folded `Constant[Set]` widens to that carrier, its members' value pins dropped
|
|
21
|
+
# ({MethodDispatcher.array_new_element}) and joined with the `Dynamic[top]` floor;
|
|
22
|
+
# - a typed `Set` whose element already carries a gradual arm re-grows the way
|
|
23
|
+
# {MutationRejoin.regrowable_carrier?} lets an `Array` / `Hash` re-grow;
|
|
24
|
+
# - a PRECISE `Set[String]` (an RBS return such as `Array#to_set`) re-grows too (issue #2361), as a precise
|
|
25
|
+
# `Array[String]` does ({MutationRejoin.widen_precise_array}): `f = ARGV.to_set; f.clear; f << 1` is
|
|
26
|
+
# `Set[Dynamic[top] | String]`.
|
|
27
|
+
#
|
|
28
|
+
# A gradual seed admits every added member as itself: the seed-admissibility gate has nothing to contradict on
|
|
29
|
+
# it. A precise seed admits only a member its element accepts ({MutationRejoin.precise_admitted}), any other
|
|
30
|
+
# taking the floor, and takes no {RewriteMutation} arm: `replace` joins its elements onto the seed, and `map!`
|
|
31
|
+
# leaves it as it was. Every join carries the `Dynamic[top]` floor, as the Array re-join's does. An adder whose
|
|
32
|
+
# arguments the block-capture seam did not type joins only that floor: the store happened, and a Set has no
|
|
33
|
+
# block content join to add it afterwards. An adder called with no arguments changes nothing.
|
|
34
|
+
module SetMutation
|
|
35
|
+
# Receiver-mutating Set methods. The non-bang siblings (`map` vs `map!`, `select` vs `select!`) and the
|
|
36
|
+
# algebra (`|`, `+`, `-`, `&`, `^`) return a new Set and stay precise.
|
|
37
|
+
MUTATORS = %i[
|
|
38
|
+
<< add add? merge replace
|
|
39
|
+
map! collect! flatten!
|
|
40
|
+
clear delete delete? delete_if keep_if select! filter! reject! subtract
|
|
41
|
+
].to_set.freeze
|
|
42
|
+
|
|
43
|
+
# The {MUTATORS} that put elements in. `<<` / `add` / `add?` store their argument; `merge` stores every
|
|
44
|
+
# argument's elements; `replace` its argument's elements, after {RewriteMutation} has dropped the old ones.
|
|
45
|
+
CONTENT_ADDERS = %i[<< add add? merge replace].to_set.freeze
|
|
46
|
+
|
|
47
|
+
module_function
|
|
48
|
+
|
|
49
|
+
def constant?(type)
|
|
50
|
+
type.is_a?(Type::Constant) && type.value.is_a?(::Set)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# True when `type` is a `Set` carrier of either kind, which {#widen} answers for alone.
|
|
54
|
+
def set?(type)
|
|
55
|
+
constant?(type) || (type.is_a?(Type::Nominal) && type.class_name == "Set")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# True when `type` is a `Set` the seams may grow: a constant-folded one, or a typed one carrying a gradual
|
|
59
|
+
# element arm.
|
|
60
|
+
def carrier?(type)
|
|
61
|
+
constant?(type) || (type.is_a?(Type::Nominal) && regrowable?(type))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# True when `type`, or a member of it, is a {#carrier?}: the only types a Set-only adder's floor can change.
|
|
65
|
+
def carrier_in?(type)
|
|
66
|
+
type.is_a?(Type::Union) ? type.members.any? { |member| carrier?(member) } : carrier?(type)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Every typed `Set[E]`, a precise one included since issue #2361.
|
|
70
|
+
def regrowable?(nominal)
|
|
71
|
+
nominal.class_name == "Set" && nominal.type_args.size == 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The carrier `type` holds after `method_name` with argument types `arg_types`, or `nil` when the call is not
|
|
75
|
+
# a Set mutator or `type` is not a carrier this seam grows. `nil` also answers a re-join that changed nothing,
|
|
76
|
+
# so a remover on an already-widened Set does not rebind it. That is a guard, not a behaviour: a Set has no
|
|
77
|
+
# `receiver[key] ||=` slot narrowings for a rebind to drop, so the identical carrier would read the same either
|
|
78
|
+
# way, and declining only saves rebuilding the scope.
|
|
79
|
+
#
|
|
80
|
+
# Issue #2555 L3 — `synthesised` says the binding carries the `:synthesised` construction fact: the engine built
|
|
81
|
+
# the Set itself (`s = Set[1]`, `Set.new(...)`), so no declaration made a claim about its elements, and the
|
|
82
|
+
# carrier stays re-growable for as long as the binding keeps the fact. Such a Set joins without the floor
|
|
83
|
+
# ({#floored}'s and {#join}'s) and without the precise seed's admissibility gate: `s = Set[1]; s << 2` reads
|
|
84
|
+
# `Set[Integer]`, and a read of its member reports what an Integer does not answer.
|
|
85
|
+
def widen(type, method_name, arg_types, unread_arguments: false, synthesised: false)
|
|
86
|
+
return nil unless MUTATORS.include?(method_name)
|
|
87
|
+
|
|
88
|
+
if constant?(type)
|
|
89
|
+
members = type.value.map { |v| Type::Combinator.constant_of(v) }
|
|
90
|
+
seed = synthesised && !members.empty? ? exact(members) : floored(members)
|
|
91
|
+
join(seed, method_name, arg_types, unread_arguments, synthesised:)
|
|
92
|
+
elsif type.is_a?(Type::Nominal) && regrowable?(type)
|
|
93
|
+
joined = join(type, method_name, arg_types, unread_arguments, synthesised:)
|
|
94
|
+
joined == type ? nil : joined
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Every join carries the `Dynamic[top]` floor, a precise seed's included (issue #2361): a later remover or
|
|
99
|
+
# rewriter (`subtract`, `delete`, `map!`) can take the stored members out again, and nothing narrows the
|
|
100
|
+
# carrier back, so a closed `Set[Integer | String]` would reject a declared `Set[String]` the program meets.
|
|
101
|
+
# A synthesised carrier (issue #2555) takes the rewrite arm a gradual one takes, admits every added member, and
|
|
102
|
+
# joins no floor: an untyped store joins `Dynamic[top]` as the one unknown member it is.
|
|
103
|
+
def join(carrier, method_name, arg_types, unread_arguments, synthesised: false)
|
|
104
|
+
seed = carrier.type_args.first
|
|
105
|
+
# A precise seed takes no rewrite arm, as a precise `Array` does not ({MutationRejoin.widen_precise_array}).
|
|
106
|
+
carrier = RewriteMutation.arm(carrier, method_name) if synthesised || MutationRejoin.gradual_parameter?(seed)
|
|
107
|
+
return carrier unless CONTENT_ADDERS.include?(method_name)
|
|
108
|
+
|
|
109
|
+
stored = added_elements(method_name, arg_types)
|
|
110
|
+
if stored.empty?
|
|
111
|
+
# An adder called with no arguments (`s.merge`) is a no-op; one whose arguments went untyped is a store.
|
|
112
|
+
return carrier unless unread_arguments
|
|
113
|
+
|
|
114
|
+
added = []
|
|
115
|
+
else
|
|
116
|
+
return carrier if !MutationRejoin.gradual_parameter?(seed) && MutationRejoin.already_held?(seed, stored)
|
|
117
|
+
|
|
118
|
+
added = admitted(seed, stored.map { |t| widened_member(t) }, synthesised:)
|
|
119
|
+
end
|
|
120
|
+
floor = synthesised && !added.empty? ? [] : [Type::Combinator.untyped]
|
|
121
|
+
element = Type::Combinator.union(carrier.type_args.first, *added, *floor)
|
|
122
|
+
Type::Combinator.nominal_of("Set", type_args: [element])
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# `added` as {MutationRejoin.precise_admitted} admits it into a precise seed; a gradual or synthesised seed
|
|
126
|
+
# admits all.
|
|
127
|
+
def admitted(seed, added, synthesised: false)
|
|
128
|
+
return added if synthesised || MutationRejoin.gradual_parameter?(seed)
|
|
129
|
+
|
|
130
|
+
MutationRejoin.precise_admitted(seed, added)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The element types one content-adder call puts in, from the call's argument types.
|
|
134
|
+
def added_elements(method_name, arg_types)
|
|
135
|
+
return [] if arg_types.empty?
|
|
136
|
+
|
|
137
|
+
case method_name
|
|
138
|
+
when :merge, :replace then arg_types.flat_map { |t| element_types(t) }
|
|
139
|
+
else [arg_types.first || Type::Combinator.untyped]
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# `Set[E]` over `members`, value pins dropped: a constant Set a synthesised binding holds (issue #2555).
|
|
144
|
+
def exact(members)
|
|
145
|
+
Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.union(*members.map { |m| widened_member(m) })])
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# `Set[E | Dynamic[top]]` over `members`, the #2290 typed-Set floor. An empty seed (`Set.new`) is
|
|
149
|
+
# `Set[Dynamic[top]]`.
|
|
150
|
+
def floored(members)
|
|
151
|
+
widened = members.map { |m| widened_member(m) }
|
|
152
|
+
Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.union(*widened, Type::Combinator.untyped)])
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# The element types an enumerable argument (`merge`'s, `replace`'s, the algebra's `_Each[A]`) yields: a
|
|
156
|
+
# `Tuple`'s elements, an `Array` / `Set` / `Range` nominal's element parameter, a constant Set's or Range's
|
|
157
|
+
# members. Anything else yields elements this seam cannot name, which are unknown rather than none.
|
|
158
|
+
def element_types(type)
|
|
159
|
+
case type
|
|
160
|
+
when Type::Union then type.members.flat_map { |m| element_types(m) }
|
|
161
|
+
when Type::Tuple then type.elements
|
|
162
|
+
when Type::Nominal
|
|
163
|
+
if %w[Array Set Range].include?(type.class_name) && type.type_args.size == 1
|
|
164
|
+
type.type_args
|
|
165
|
+
else
|
|
166
|
+
[Type::Combinator.untyped]
|
|
167
|
+
end
|
|
168
|
+
when Type::Constant then constant_elements(type)
|
|
169
|
+
else [Type::Combinator.untyped]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def constant_elements(constant)
|
|
174
|
+
value = constant.value
|
|
175
|
+
if value.is_a?(::Set) then value.map { |v| Type::Combinator.constant_of(v) }
|
|
176
|
+
elsif value.is_a?(::Range) && value.begin.is_a?(Integer)
|
|
177
|
+
[Type::Combinator.nominal_of("Integer")]
|
|
178
|
+
else [Type::Combinator.untyped]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# A stored member's value pin and literal shape are dropped, as an `Array.new` fill's are: the set holds the
|
|
183
|
+
# object, and the program keeps a reference to it.
|
|
184
|
+
def widened_member(type)
|
|
185
|
+
MethodDispatcher.array_new_element(type)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -37,7 +37,8 @@ module Rigor
|
|
|
37
37
|
# Known remainders, pinned in `spec/integration/definer_resolution_witness_spec.rb`: `class << self; prepend
|
|
38
38
|
# P` is recorded as an `extend` (the chain places `P` after the singleton, so an own `def self.x` is trusted
|
|
39
39
|
# against it); a concern is capable for every name; an own `def self.x` is trusted against its own level's
|
|
40
|
-
# U2 hooks and same-class redefinitions
|
|
40
|
+
# U2 hooks and same-class redefinitions, except a concern block's `def self.x` of the same name (#3005, which
|
|
41
|
+
# the read tests); a `class Class; def inherited` monkeypatch is unseen; and a
|
|
41
42
|
# superclass that only `extend`s adds two forks, so every read on its subclasses declines.
|
|
42
43
|
module SingletonHookDecline
|
|
43
44
|
WILDCARD = "*"
|
|
@@ -65,7 +66,8 @@ module Rigor
|
|
|
65
66
|
entry = chain.entries[index]
|
|
66
67
|
# A singleton project entry on a singleton chain is always a level's class entry.
|
|
67
68
|
own = !entry.external? && entry.side == :singleton && def_head(scope, entry.name, method_name) == entry.name
|
|
68
|
-
own
|
|
69
|
+
# Issue #3005 — an own `def self.x` that a concern block's `def self.x` on its level may replace is tested.
|
|
70
|
+
own && !chain.hook_block_rival?(scope, index, method_name) ? index : index + 1
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
# What a class entry's answer is when its `def` was written in another module's body: `:copy` when that
|
|
@@ -92,21 +94,28 @@ module Rigor
|
|
|
92
94
|
# Whether the class's singleton `def` is the very node `head`'s instance table holds, the one the fold
|
|
93
95
|
# copied: a receiverless `def`. A `def C.x` or `class << C; def x; end` written inside `head`'s body has the
|
|
94
96
|
# same nesting head and is the class's own, not a copy. (The walk records a `def C.x` written in `module X`
|
|
95
|
-
# as `X#x` too, so identity alone does not tell it from a copy; its receiver does.)
|
|
97
|
+
# as `X#x` too, so identity alone does not tell it from a copy; its receiver does.) Issue #3155 — the walk no
|
|
98
|
+
# longer files that `def C.x` anywhere, so the copy is all the table holds; the definer's mark on the class
|
|
99
|
+
# (`ENVELOPE_OBJECT_SINGLETON_KIND`, {ScopeIndexer#record_object_singleton}) still says the class has its own.
|
|
96
100
|
def copied_from?(scope, head, method_name, owner)
|
|
97
101
|
node = scope.singleton_def_for(owner, method_name)
|
|
98
102
|
return false if node.nil? || !node.receiver.nil?
|
|
103
|
+
return false if foreign_singleton_definer?(scope, owner, method_name)
|
|
99
104
|
|
|
100
105
|
scope.user_def_for(head, method_name).equal?(node)
|
|
101
106
|
end
|
|
102
107
|
|
|
108
|
+
def foreign_singleton_definer?(scope, owner, method_name)
|
|
109
|
+
key = [Scope::DiscoveryIndex::ENVELOPE_OBJECT_SINGLETON_KIND, method_name.to_sym]
|
|
110
|
+
scope.parameter_envelopes_of(owner).key?(key)
|
|
111
|
+
end
|
|
112
|
+
|
|
103
113
|
# The innermost `Module.nesting` entry `owner`'s singleton `def` of the name was written in, or nil.
|
|
104
114
|
def def_head(scope, owner, method_name)
|
|
105
115
|
node = scope.singleton_def_for(owner, method_name)
|
|
106
116
|
return nil if node.nil?
|
|
107
117
|
|
|
108
|
-
|
|
109
|
-
nesting&.first
|
|
118
|
+
scope.discovery.recorded_def_nesting(node)&.first
|
|
110
119
|
end
|
|
111
120
|
|
|
112
121
|
# Issue #1722 — the hook-capable test for one chain entry, asked by `call.undefined-method` of a project module
|
|
@@ -171,15 +180,40 @@ module Rigor
|
|
|
171
180
|
|
|
172
181
|
# The memoised `[verdict, edges]` of one test, its edges replayed while a recording is active.
|
|
173
182
|
def verdict(kind, key)
|
|
174
|
-
|
|
183
|
+
memo_key = [kind, key]
|
|
184
|
+
entry = @memo[memo_key]
|
|
185
|
+
if entry.nil?
|
|
175
186
|
edges = []
|
|
176
|
-
value = yield(edges)
|
|
177
|
-
[value ? true : false, edges.concat(own_edges(kind, key)).uniq.freeze].freeze
|
|
187
|
+
value = MemoAudit.key_audit("singleton hook verdict") { yield(edges) }
|
|
188
|
+
entry = @memo[memo_key] = [value ? true : false, edges.concat(own_edges(kind, key)).uniq.freeze].freeze
|
|
189
|
+
else
|
|
190
|
+
entry = MemoAudit.check("singleton hook verdict", entry, kind, key) { fresh_verdict(kind, key) }
|
|
178
191
|
end
|
|
192
|
+
verdict, edges = entry
|
|
179
193
|
replay(edges) if @recording
|
|
180
194
|
verdict
|
|
181
195
|
end
|
|
182
196
|
|
|
197
|
+
# ADR-129 G2 — a memoised verdict rebuilt for the audit by a new Context, whose memo is a fresh bucket's
|
|
198
|
+
# while the recomputation runs ({Scope::ResolutionChain.hook_memo}), its dependency reads withheld.
|
|
199
|
+
def fresh_verdict(kind, key)
|
|
200
|
+
Analysis::DependencyRecorder.withhold { Context.new(@scope, @flavor).verdict_entry(kind, key) }.first
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
protected
|
|
204
|
+
|
|
205
|
+
# The `[verdict, edges]` entry of one test, computed through {#verdict} on this Context's memo.
|
|
206
|
+
def verdict_entry(kind, key)
|
|
207
|
+
case kind
|
|
208
|
+
when :project then verdict(:project, key) { project_capable?(key) }
|
|
209
|
+
when :external then verdict(:external, key) { external_capable?(key) }
|
|
210
|
+
else instance_level_capable?(key)
|
|
211
|
+
end
|
|
212
|
+
@memo[[kind, key]]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
|
|
183
217
|
def own_edges(kind, key)
|
|
184
218
|
return entry_edges(key) if kind == :external
|
|
185
219
|
return EMPTY_EDGES if kind == :instance_level
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../source/constant_path"
|
|
4
|
+
require_relative "../source/parameter_envelope"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -45,18 +46,47 @@ module Rigor
|
|
|
45
46
|
name = receiver_constant_name(call_node, receiver_type)
|
|
46
47
|
return false if name.nil?
|
|
47
48
|
|
|
48
|
-
scope.discovered_method?(name, method_name, :singleton)
|
|
49
|
+
scope.discovered_method?(name, method_name, :singleton) || foreign_singleton?(scope, name, method_name)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Issue #3016 — `def Const.x` written outside `Const`'s own body, and `Const.define_singleton_method(:x)`, record
|
|
53
|
+
# no `:singleton` row: {ScopeIndexer} files them as an opaque `[:singleton, x]` envelope on every name the
|
|
54
|
+
# constant can denote from the def site. That envelope is the evidence the method exists on the constant's
|
|
55
|
+
# object. The read records the constant's class edge, and that edge's file set holds every file writing such a
|
|
56
|
+
# def or call, so adding or removing one re-checks the call.
|
|
57
|
+
#
|
|
58
|
+
# A computed `Const.define_singleton_method(name)` may define any name, so the singleton side's open-names mark it
|
|
59
|
+
# files declines every call through the constant.
|
|
60
|
+
def foreign_singleton?(scope, name, method_name)
|
|
61
|
+
bucket = scope.parameter_envelopes_of(name)
|
|
62
|
+
bucket.key?([:singleton, method_name.to_sym]) ||
|
|
63
|
+
bucket.key?(Scope::DiscoveryIndex::ENVELOPE_OPEN_SINGLETON_NAMES_MARK)
|
|
49
64
|
end
|
|
50
65
|
|
|
51
66
|
# The `Prism::DefNode` for `method_name` in the constant's singleton body, or nil. The inference tier's
|
|
52
67
|
# entry point.
|
|
68
|
+
#
|
|
69
|
+
# Issue #3018 — nil too when another definer of the name on the constant may replace that def: a `def Const.x`,
|
|
70
|
+
# a `Const.define_singleton_method(:x)` or a `singleton_class.class_eval` def joins an opaque envelope into the
|
|
71
|
+
# name's `[:singleton, x]` slot, and a computed `define_singleton_method` files the open-names mark. The body
|
|
72
|
+
# def's own contribution is its parameter envelope, never opaque, so a lone body def keeps its typed answer.
|
|
73
|
+
# Declined whatever the source order, since a two-file project has none.
|
|
53
74
|
def def_node_for(call_node, receiver_type, method_name, scope)
|
|
54
75
|
return nil if scope.nil?
|
|
55
76
|
|
|
56
77
|
name = receiver_constant_name(call_node, receiver_type)
|
|
57
78
|
return nil if name.nil?
|
|
58
79
|
|
|
59
|
-
scope.singleton_def_for(name, method_name)
|
|
80
|
+
def_node = scope.singleton_def_for(name, method_name)
|
|
81
|
+
return nil if def_node.nil? || replaceable?(scope, name, method_name)
|
|
82
|
+
|
|
83
|
+
def_node
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def replaceable?(scope, name, method_name)
|
|
87
|
+
bucket = scope.parameter_envelopes_of(name)
|
|
88
|
+
Source::ParameterEnvelope.opaque?(bucket[[:singleton, method_name.to_sym]]) ||
|
|
89
|
+
bucket.key?(Scope::DiscoveryIndex::ENVELOPE_OPEN_SINGLETON_NAMES_MARK)
|
|
60
90
|
end
|
|
61
91
|
end
|
|
62
92
|
end
|