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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../typing_state"
|
|
4
|
+
require_relative "../memo_audit"
|
|
5
|
+
|
|
3
6
|
module Rigor
|
|
4
7
|
class Scope
|
|
5
8
|
# ADR-24 (amended for #1567, #1568, #1570 and #1571) — Ruby's method resolution order for one project class or
|
|
@@ -90,6 +93,39 @@ module Rigor
|
|
|
90
93
|
# when the entry is interned.
|
|
91
94
|
Entry = Data.define(:name, :side, :raw, :candidates, :superclass_edge, :last_segment, :owner) do
|
|
92
95
|
def external? = name.nil?
|
|
96
|
+
|
|
97
|
+
# Issue #3156 — the BODY-LESS class or module the project declares that an external entry names, or nil: the
|
|
98
|
+
# first of its spellings that is a `discovered_classes` key, read in Ruby's lookup order. Such a declaration
|
|
99
|
+
# records no method, superclass or include (`module PMod; end`), which is why the chain holds it as external.
|
|
100
|
+
# The owner-qualified spelling (#1698) leads only on a mixin edge: Ruby evaluates a superclass expression
|
|
101
|
+
# outside the class's own body, so `class Leaf < Base` never names `Leaf::Base`. Any spelling up to the match
|
|
102
|
+
# that RBS alone knows, that a project constant write binds (`AMid = ::Other` in `NS`), or that names a class
|
|
103
|
+
# with a body leaves nil, since Ruby may resolve the constant there or the chain should have held it. It files
|
|
104
|
+
# the entry's negative class and constant edges, which a declaration or write of the name appearing or
|
|
105
|
+
# vanishing fires.
|
|
106
|
+
def declared_name(scope)
|
|
107
|
+
spelling = raw.to_s
|
|
108
|
+
Analysis::DependencyRecorder.read_last_segment(:class, spelling)
|
|
109
|
+
Analysis::DependencyRecorder.read_last_segment(:constant, spelling)
|
|
110
|
+
own = owner && !superclass_edge && !spelling.start_with?("::")
|
|
111
|
+
(own ? ["#{owner}::#{spelling}", *candidates] : candidates).uniq.each do |candidate|
|
|
112
|
+
return nil if constant_written?(scope, candidate)
|
|
113
|
+
return (scope.known_user_class?(candidate) ? nil : candidate) if scope.discovered_classes.key?(candidate)
|
|
114
|
+
return nil if Rigor::Reflection.rbs_class_known?(candidate, scope: scope)
|
|
115
|
+
end
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
# Whether a project constant write other than a class declaration binds `name` exactly (the census keeps a
|
|
122
|
+
# write as spelled, a `*::` wildcard for one it cannot place), or a literal `const_set` in this file does
|
|
123
|
+
# (`LastLine::SelfEvidence#const_set_spellings`; the census files none).
|
|
124
|
+
def constant_written?(scope, name)
|
|
125
|
+
(scope.bound_constant_names(name) + scope.shadowing_constant_names(name)).any? do |written|
|
|
126
|
+
written == name || written.start_with?("*::")
|
|
127
|
+
end || scope.discovery.implicit_self_evidence&.const_set_spellings&.include?(name) || false
|
|
128
|
+
end
|
|
93
129
|
end
|
|
94
130
|
|
|
95
131
|
# How many project entries a chain may hold before it is cut (ADR-41 WD4). The breadth-first walks
|
|
@@ -102,9 +138,20 @@ module Rigor
|
|
|
102
138
|
# the multi-file rule fired (two or more files declare it and it has two or more edges on that side).
|
|
103
139
|
# Interned per `[node, kind]` in the flavor's bucket, so equal marks are one object. Kind `:extend_hook`
|
|
104
140
|
# (#1730, listing `"*"`) marks a node a module it extends may mix into through a hook; it is not an order mark
|
|
105
|
-
# ({#order_marks}).
|
|
141
|
+
# ({#order_marks}). Kind `:include_hook` (#1592, listing `"*"` and `"?"`) marks a class object a module it
|
|
142
|
+
# includes or prepends may extend, through a hook, with a module no table names; nor is it. Kind `:hook_order`
|
|
143
|
+
# (#1592, listing nothing) marks a class object whose own `extend`s and hook-extended modules have no known
|
|
144
|
+
# order between them: the fork it counts already settles the chain, and the mark tells the typing read why.
|
|
145
|
+
# Kind `:singleton_prepend` (#2998, listing nothing) marks a class object with singleton-side prepends of its
|
|
146
|
+
# own, which master's order does not place, and kind `:prepend_order` (#2998, listing nothing) one whose
|
|
147
|
+
# singleton-side prepends have no known order among themselves; neither is an order mark. Kind `:unnamed_prepend`
|
|
148
|
+
# (#3020, listing nothing) marks a class object with a singleton-side prepend of a module no table names, which
|
|
149
|
+
# is not an order mark either.
|
|
106
150
|
Mark = Data.define(:node, :kind, :listed, :multi_file)
|
|
107
151
|
|
|
152
|
+
# The mark kinds that are not order marks.
|
|
153
|
+
HOOK_MARK_KINDS = %i[extend_hook include_hook hook_order singleton_prepend prepend_order unnamed_prepend].freeze
|
|
154
|
+
|
|
108
155
|
NO_MARKS = [].freeze
|
|
109
156
|
|
|
110
157
|
attr_reader :root, :side, :entries, :level_starts, :level_classes, :flavor
|
|
@@ -131,6 +178,13 @@ module Rigor
|
|
|
131
178
|
# "no definer" from "a definer past the cut", which is budget uncertainty, not absence.
|
|
132
179
|
def truncated? = @truncated
|
|
133
180
|
|
|
181
|
+
# ADR-129 G2 — the chain's content, for the memo audit: a recomputation builds a new chain object, which
|
|
182
|
+
# equals the stored one field for field (its retro world included) when the memo's key holds every input.
|
|
183
|
+
def audit_signature
|
|
184
|
+
[@root, @side, @entries, @level_starts, @level_classes, @levels_end, @truncated, @forks, @marks, @flavor,
|
|
185
|
+
@retro&.audit_signature]
|
|
186
|
+
end
|
|
187
|
+
|
|
134
188
|
# How many forks the replay met while this chain was built, counted across every module sub-chain it drew
|
|
135
189
|
# on (a memoised sub-chain hands its own count up): each entry that reached the chain by a second route.
|
|
136
190
|
# An over-count only ever sends a reader to master's answer.
|
|
@@ -147,13 +201,57 @@ module Rigor
|
|
|
147
201
|
# those readers to master's order would answer a constant lookup breadth-first, which is wrong for the
|
|
148
202
|
# #1567 shape on every class that extends such a module.
|
|
149
203
|
def order_marks
|
|
150
|
-
@memo[:order_marks] ||= @marks.reject { |mark| mark.kind
|
|
204
|
+
@memo[:order_marks] ||= @marks.reject { |mark| HOOK_MARK_KINDS.include?(mark.kind) }.freeze
|
|
151
205
|
end
|
|
152
206
|
|
|
153
207
|
# True when a class on the chain records a mixin the tables cannot name (`"*"`: an `include` of a
|
|
154
208
|
# non-constant, or a call the walk cannot record), so the chain may hold a module it does not list.
|
|
155
209
|
def wildcard_mixin? = @marks.any? { |mark| mark.listed.include?(Relevance::WILDCARD) }
|
|
156
210
|
|
|
211
|
+
# Issue #2991 — true when a class on the chain mixes in a module the tables do not record under any name
|
|
212
|
+
# (`include(*MODS)`, `include helper`, a hook's `base.include M`, an unrecorded `self.include M`): the chain's SET
|
|
213
|
+
# of modules is unknown, not only its order. Every such mark is also a {#wildcard_mixin?} one; `include M;
|
|
214
|
+
# prepend M` taints the order alone.
|
|
215
|
+
def unnamed_mixin? = @marks.any? { |mark| mark.listed.include?(Relevance::UNNAMED) }
|
|
216
|
+
|
|
217
|
+
# Issue #1592 — whether a class object on the chain has hook-extended modules whose order against its own
|
|
218
|
+
# `extend`s, or among themselves, is not a fact ({Builder#singleton_mixins}).
|
|
219
|
+
def hook_order_marked? = @marks.any? { |mark| mark.kind == :hook_order }
|
|
220
|
+
|
|
221
|
+
# Issue #2998 — whether a class object on the chain has singleton-side prepends of its own
|
|
222
|
+
# ({Builder#own_singleton_prepends}), which master's order does not place.
|
|
223
|
+
def singleton_prepend_marked? = @marks.any? { |mark| mark.kind == :singleton_prepend }
|
|
224
|
+
|
|
225
|
+
# Issue #2998 — the class objects on the chain whose prepend region has no known order
|
|
226
|
+
# ({Builder#order_prepends}).
|
|
227
|
+
def prepend_order_nodes
|
|
228
|
+
@memo[:prepend_order_nodes] ||= @marks.filter_map { |mark| mark.node if mark.kind == :prepend_order }.freeze
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Issue #3020 — the class objects on the chain with a singleton-side prepend of a module no table names
|
|
232
|
+
# ({Builder#own_singleton_prepends}).
|
|
233
|
+
def unnamed_prepend_nodes
|
|
234
|
+
@memo[:unnamed_prepend_nodes] ||= @marks.filter_map { |mark| mark.node if mark.kind == :unnamed_prepend }.freeze
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Issue #3020 — the class objects on the chain whose own singleton side lists a module no table names
|
|
238
|
+
# (`extend(*MODS)`, `class << self; include(*MODS)`, `singleton_class.prepend(*MODS)`: the `:extend` order mark
|
|
239
|
+
# listing {Relevance::UNNAMED}).
|
|
240
|
+
def unnamed_extend_nodes
|
|
241
|
+
@memo[:unnamed_extend_nodes] ||= @marks.filter_map do |mark|
|
|
242
|
+
mark.node if mark.kind == :extend && mark.listed.include?(Relevance::UNNAMED)
|
|
243
|
+
end.freeze
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# The chain's retro world ({#settle}), or nil when it has none.
|
|
247
|
+
def retro_world = @retro
|
|
248
|
+
|
|
249
|
+
# Issue #1592 — the class objects on a singleton chain that a module they include or prepend may extend, through
|
|
250
|
+
# a hook, with a module no table names: that module sits right after the class object's own entry.
|
|
251
|
+
def hook_open_nodes
|
|
252
|
+
@memo[:hook_open_nodes] ||= @marks.filter_map { |mark| mark.node if mark.kind == :include_hook }.freeze
|
|
253
|
+
end
|
|
254
|
+
|
|
157
255
|
# The ONE decision every first-definer reader makes: does `answer`, read off this chain, stand, or does
|
|
158
256
|
# the reader answer what the walk this chain replaced answered ({MasterOrder})? Returns `:chain` or
|
|
159
257
|
# `:master`.
|
|
@@ -222,18 +320,30 @@ module Rigor
|
|
|
222
320
|
# it does not head the part searched (its prepends, or its own edges when the search starts past it,
|
|
223
321
|
# placed the rest). The entry that answers records none of its own: its method edge is the caller's
|
|
224
322
|
# `Scope#user_def_for`. A read that finds nothing has passed every entry.
|
|
323
|
+
#
|
|
324
|
+
# Issue #2998 — on a singleton chain a NEW file reopening a class object's class can prepend to its singleton
|
|
325
|
+
# (`class << self; prepend P`), ahead of everything the class object's level holds, which no class edge of the
|
|
326
|
+
# declaring files sees. So each class object the search passes, and one that answers, also files the name edge
|
|
327
|
+
# `mark:<last segment>` ({#record_prepend_regions}), which `IncrementalSession#mark_affected` fires when a
|
|
328
|
+
# `[ENVELOPE_SINGLETON_PREPEND_KIND, …]` entry (a diffed class mark) appears on or vanishes from the class, and
|
|
329
|
+
# not on a plain reopening. A class object behind the answer is filed through {#settle}'s filing, and so is every
|
|
330
|
+
# one on a read that finds nothing.
|
|
225
331
|
def search(scope, start = 0, stop = nil, side: nil)
|
|
226
332
|
recording = Analysis::DependencyRecorder.active?
|
|
227
333
|
stop ||= @entries.size
|
|
228
334
|
if recording
|
|
229
335
|
record_head(scope, start, side)
|
|
230
336
|
record_extend_hooks(scope)
|
|
337
|
+
record_include_hooks(scope)
|
|
231
338
|
end
|
|
232
339
|
index = start
|
|
233
340
|
while index < stop
|
|
234
341
|
entry = @entries[index]
|
|
235
342
|
answer = side.nil? || entry.side == side ? yield(entry) : nil
|
|
236
|
-
|
|
343
|
+
if answer
|
|
344
|
+
record_prepend_regions(start, index) if recording
|
|
345
|
+
return answer
|
|
346
|
+
end
|
|
237
347
|
|
|
238
348
|
ResolutionChain.record_entry(scope, entry, side) if recording
|
|
239
349
|
index += 1
|
|
@@ -241,6 +351,19 @@ module Rigor
|
|
|
241
351
|
nil
|
|
242
352
|
end
|
|
243
353
|
|
|
354
|
+
# The `mark:` name edges of the class objects on a singleton chain from `start` through `last`.
|
|
355
|
+
def record_prepend_regions(start, last)
|
|
356
|
+
return unless @side == :singleton
|
|
357
|
+
|
|
358
|
+
(start..last).each do |index|
|
|
359
|
+
entry = @entries[index]
|
|
360
|
+
next unless entry.side == :singleton && !entry.external?
|
|
361
|
+
|
|
362
|
+
Analysis::DependencyRecorder.read_last_segment(:mark, entry.name)
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
private :record_prepend_regions
|
|
366
|
+
|
|
244
367
|
# Records the ADR-46 class edge of the root and of every project entry ahead of `stop` (on `side`, when
|
|
245
368
|
# given; every entry when `stop` is nil) — the dependency of a read that consulted those positions.
|
|
246
369
|
def record(scope, stop = nil, side: nil)
|
|
@@ -249,6 +372,21 @@ module Rigor
|
|
|
249
372
|
ResolutionChain.record_class(scope, @root)
|
|
250
373
|
(stop || @entries.size).times { |index| ResolutionChain.record_entry(scope, @entries[index], side) }
|
|
251
374
|
record_extend_hooks(scope)
|
|
375
|
+
record_include_hooks(scope)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Issue #2689 — the negative edge a method-walk MISS owes each EXTERNAL entry: `class:<last segment>` of the name
|
|
379
|
+
# as written. An external entry is an ancestor the project declares with no methods (the usual empty
|
|
380
|
+
# `ApplicationRecord`) or not at all yet, so the walk looked nothing up there; a later file that declares it
|
|
381
|
+
# with the method turns the miss, and the declaration appearing is the one event that file fires. A core or gem
|
|
382
|
+
# ancestor (`StandardError`, `Comparable`) files its key too, which fires only on a file that declares a
|
|
383
|
+
# constant of that name. Recorded only on a miss: a hit's answer cannot be turned by an entry gaining a method.
|
|
384
|
+
def record_external_absence
|
|
385
|
+
return unless Analysis::DependencyRecorder.active?
|
|
386
|
+
|
|
387
|
+
@entries.each do |entry|
|
|
388
|
+
Analysis::DependencyRecorder.read_last_segment(:class, entry.raw.to_s) if entry.external?
|
|
389
|
+
end
|
|
252
390
|
end
|
|
253
391
|
|
|
254
392
|
# What a {#settle} verdict adds to what the search already filed. The search files the root and every entry
|
|
@@ -277,6 +415,7 @@ module Rigor
|
|
|
277
415
|
Analysis::DependencyRecorder.file_chain_once(self, start, scope.discovery) { file_beyond(scope, start) }
|
|
278
416
|
record_hooks(scope)
|
|
279
417
|
record_extend_hooks(scope)
|
|
418
|
+
record_include_hooks(scope)
|
|
280
419
|
end
|
|
281
420
|
private :record_beyond
|
|
282
421
|
|
|
@@ -323,7 +462,7 @@ module Rigor
|
|
|
323
462
|
end
|
|
324
463
|
private :record_hooks
|
|
325
464
|
|
|
326
|
-
# #1730 — the edges of the {Builder#extend_hook_taint
|
|
465
|
+
# #1730 — the edges of the {Builder#extend_hook_taint} decision: an instance chain's project entries may carry
|
|
327
466
|
# a `"*"` mark because a module one of them `extend`s has a hook that mixes into the extender, and the
|
|
328
467
|
# module is often declared in a file the chain records nothing of. Each such module the decision read files
|
|
329
468
|
# its class edges (an edit to its hook moves its declaration signature) and the negative method edges of
|
|
@@ -350,6 +489,168 @@ module Rigor
|
|
|
350
489
|
end
|
|
351
490
|
private :record_extend_hooks
|
|
352
491
|
|
|
492
|
+
# #1592 — the edges of the {Builder#hook_extensions} read: a singleton chain draws, for every class on it, the
|
|
493
|
+
# modules that class's direct mixins' hooks extend it with, and the mixins are declared in files the chain's own
|
|
494
|
+
# entries record nothing of. Each resolved mixin files its class edges and the negative `mark:` edge on its name,
|
|
495
|
+
# which an edit adding or dropping a hook-extend entry fires; every mixin, and every module a hook extends by
|
|
496
|
+
# name, files the negative class edge on its last segment, which a NEW file declaring or reopening it fires.
|
|
497
|
+
# Filed once per consumer and chain.
|
|
498
|
+
def record_include_hooks(scope)
|
|
499
|
+
return unless @side == :singleton
|
|
500
|
+
|
|
501
|
+
edges = (@memo[:include_hook_edges] ||= ResolutionChain.include_hook_edges(scope, @flavor, @level_classes))
|
|
502
|
+
return if edges.empty?
|
|
503
|
+
|
|
504
|
+
Analysis::DependencyRecorder.file_chain_once(self, :include_hooks, scope.discovery) do
|
|
505
|
+
edges.each do |kind, name|
|
|
506
|
+
ResolutionChain.record_class(scope, name) if kind == :mixin
|
|
507
|
+
Analysis::DependencyRecorder.read_last_segment(:mark, name) if kind == :mixin
|
|
508
|
+
Analysis::DependencyRecorder.read_last_segment(:class, name)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
private :record_include_hooks
|
|
513
|
+
|
|
514
|
+
# `[[:mixin, resolved name] | [:spelling, raw], …]`: the direct mixins of every class on a singleton chain, and
|
|
515
|
+
# the modules the resolved ones' hooks extend by name ({Builder#hook_extensions}).
|
|
516
|
+
def self.include_hook_edges(scope, flavor, classes)
|
|
517
|
+
resolver = resolver_for(scope, flavor)
|
|
518
|
+
discovery = scope.discovery
|
|
519
|
+
classes.each_with_object([]) do |klass, out|
|
|
520
|
+
next if klass.nil?
|
|
521
|
+
|
|
522
|
+
(discovery.discovered_includes[klass] || EMPTY_NAMES).uniq.each do |raw|
|
|
523
|
+
mixin = resolver.resolve_declared(klass, raw)
|
|
524
|
+
next out << [:spelling, raw].freeze if mixin.nil?
|
|
525
|
+
|
|
526
|
+
out << [:mixin, mixin].freeze
|
|
527
|
+
hook_extend_keys(discovery, mixin).each { |key| out << [:spelling, key[1]].freeze }
|
|
528
|
+
concern_dependency_edges(discovery, resolver, mixin, out)
|
|
529
|
+
end
|
|
530
|
+
end.uniq.freeze
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# Issue #2992 — the edges of a concern mixin's dependency read ({.concern_dependencies}): every module its
|
|
534
|
+
# dependency entries name, a concern or not yet one (a file that adds `extend ActiveSupport::Concern` to it
|
|
535
|
+
# turns it into one), as a mixin, with the modules its hooks extend by name; a spelling no module answers files
|
|
536
|
+
# its negative class edge. A concern among them is followed further.
|
|
537
|
+
def self.concern_dependency_edges(discovery, resolver, mixin, out, seen = [mixin])
|
|
538
|
+
return unless concern_bucket?(discovery, mixin)
|
|
539
|
+
|
|
540
|
+
dependency_spellings(discovery, mixin).each do |raw|
|
|
541
|
+
dependency = resolver.resolve_declared(mixin, raw)
|
|
542
|
+
next out << [:spelling, raw].freeze if dependency.nil?
|
|
543
|
+
next if seen.include?(dependency)
|
|
544
|
+
|
|
545
|
+
seen << dependency
|
|
546
|
+
out << [:mixin, dependency].freeze
|
|
547
|
+
hook_extend_keys(discovery, dependency).each { |key| out << [:spelling, key[1]].freeze }
|
|
548
|
+
concern_dependency_edges(discovery, resolver, dependency, out, seen)
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
private_class_method :concern_dependency_edges
|
|
552
|
+
|
|
553
|
+
# The `[ENVELOPE_CONCERN_DEPENDENCY_KIND, written]` spellings of a module's bucket, nearest first (a later
|
|
554
|
+
# statement's ahead of an earlier one's).
|
|
555
|
+
def self.dependency_spellings(discovery, name)
|
|
556
|
+
bucket = discovery.discovered_parameter_envelopes[name]
|
|
557
|
+
return EMPTY_NAMES if bucket.nil?
|
|
558
|
+
|
|
559
|
+
bucket.each_key.filter_map do |key|
|
|
560
|
+
key[1] if key.is_a?(Array) && key.first == DiscoveryIndex::ENVELOPE_CONCERN_DEPENDENCY_KIND
|
|
561
|
+
end.reverse.uniq
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Issue #2992 — the concerns a concern `mixin` provably takes as dependencies (its
|
|
565
|
+
# `[ENVELOPE_CONCERN_DEPENDENCY_KIND, written]` entries: an `include` or `prepend` after its own `extend
|
|
566
|
+
# ActiveSupport::Concern`), at any depth through concerns, nearest first, without `mixin`:
|
|
567
|
+
# `Concern#append_features` records each as a dependency of `mixin` and includes it into `mixin`'s final
|
|
568
|
+
# includer, so the includer's singleton chain gains what each one's hooks extend it with. Empty when `mixin` is
|
|
569
|
+
# no concern. A dependency the walk meets twice is listed once, at its nearest place.
|
|
570
|
+
def self.concern_dependencies(discovery, resolver, mixin, seen = [mixin])
|
|
571
|
+
return EMPTY_NAMES unless concern_bucket?(discovery, mixin)
|
|
572
|
+
|
|
573
|
+
out = []
|
|
574
|
+
dependency_spellings(discovery, mixin).each do |raw|
|
|
575
|
+
dependency = resolver.resolve_declared(mixin, raw)
|
|
576
|
+
next if dependency.nil? || seen.include?(dependency) || !concern_bucket?(discovery, dependency)
|
|
577
|
+
|
|
578
|
+
seen << dependency
|
|
579
|
+
out << dependency
|
|
580
|
+
out.concat(concern_dependencies(discovery, resolver, dependency, seen))
|
|
581
|
+
end
|
|
582
|
+
out
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# The entry an `extend ActiveSupport::Concern` module records ({Inference::ScopeIndexer}).
|
|
586
|
+
CONCERN_KEY = [DiscoveryIndex::ENVELOPE_HOOK_EXTEND_KIND, "ClassMethods", :const_get].freeze
|
|
587
|
+
private_constant :CONCERN_KEY
|
|
588
|
+
|
|
589
|
+
def self.concern_bucket?(discovery, name)
|
|
590
|
+
bucket = discovery.discovered_parameter_envelopes[name]
|
|
591
|
+
!bucket.nil? && bucket.key?(CONCERN_KEY)
|
|
592
|
+
end
|
|
593
|
+
private_class_method :concern_bucket?
|
|
594
|
+
|
|
595
|
+
# Issue #2997 — whether `owner.const_get(raw)` may find the project's `candidate` (`"#{owner}::#{raw}"`): a
|
|
596
|
+
# `class_methods do … end` block's `def`s are filed on `Owner::ClassMethods` in every body of the module, but
|
|
597
|
+
# Ruby has that module only when `owner` is a concern, so one that no `module` declaration writes counts only
|
|
598
|
+
# for a concern `owner`.
|
|
599
|
+
def self.const_get_candidate?(discovery, owner, candidate)
|
|
600
|
+
concern_bucket?(discovery, owner) || discovery.discovered_classes.key?(candidate)
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# Issue #3005 — whether `name` is a module a concern block's `def self.x`s are filed on (`Owner::<included>`).
|
|
604
|
+
def self.hook_block_module?(name)
|
|
605
|
+
DiscoveryIndex::HOOK_BLOCK_SEGMENTS.any? { |segment| name.end_with?("::#{segment}") }
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Issue #3005 — whether a hook-block module on the level of the class object at `index` defines `method_name`:
|
|
609
|
+
# Ruby defines it on that class object itself when the block runs, so it and the class's own `def self.x`
|
|
610
|
+
# replace each other in an order the tables do not hold (the `include` against the `def`).
|
|
611
|
+
def hook_block_rival?(scope, index, method_name)
|
|
612
|
+
level = @level_starts.rindex { |start| start <= index }
|
|
613
|
+
return false if level.nil? || level >= level_count
|
|
614
|
+
|
|
615
|
+
level_entries(level).any? do |entry|
|
|
616
|
+
!entry.external? && entry.side == :instance && ResolutionChain.hook_block_module?(entry.name) &&
|
|
617
|
+
!scope.user_def_for(entry.name, method_name).nil?
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
GATED_HOOKS = DiscoveryIndex::CONCERN_BLOCK_HOOKS.invert.freeze
|
|
622
|
+
private_constant :GATED_HOOKS
|
|
623
|
+
|
|
624
|
+
# Issue #3005 — the hook an entry of `owner`'s bucket naming `hook` runs on: a gated concern-block hook
|
|
625
|
+
# ({DiscoveryIndex::CONCERN_BLOCK_HOOKS}) is its plain hook when `owner` is a concern and nil otherwise; any
|
|
626
|
+
# other hook is itself.
|
|
627
|
+
def self.effective_hook(discovery, owner, hook)
|
|
628
|
+
plain = GATED_HOOKS[hook]
|
|
629
|
+
return hook if plain.nil?
|
|
630
|
+
|
|
631
|
+
concern_bucket?(discovery, owner) ? plain : nil
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
HOOK_KEY_SLOTS = {
|
|
635
|
+
DiscoveryIndex::ENVELOPE_HOOK_EXTEND_KIND => 2, DiscoveryIndex::ENVELOPE_HOOK_EXTEND_OPEN_KIND => 1,
|
|
636
|
+
DiscoveryIndex::ENVELOPE_HOOK_DEF_KIND => 1
|
|
637
|
+
}.freeze
|
|
638
|
+
private_constant :HOOK_KEY_SLOTS
|
|
639
|
+
|
|
640
|
+
# The hook a hook-extend, open or def envelope key names (`:const_get` for a concern's), or nil for another key.
|
|
641
|
+
def self.hook_of(key)
|
|
642
|
+
slot = key.is_a?(Array) ? HOOK_KEY_SLOTS[key.first] : nil
|
|
643
|
+
slot && key[slot]
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
# The `[ENVELOPE_HOOK_EXTEND_KIND, written, hook]` keys of a module's envelope bucket, in recording order.
|
|
647
|
+
def self.hook_extend_keys(discovery, name)
|
|
648
|
+
bucket = discovery.discovered_parameter_envelopes[name]
|
|
649
|
+
return EMPTY_NAMES if bucket.nil?
|
|
650
|
+
|
|
651
|
+
bucket.each_key.select { |key| key.is_a?(Array) && key.first == DiscoveryIndex::ENVELOPE_HOOK_EXTEND_KIND }
|
|
652
|
+
end
|
|
653
|
+
|
|
353
654
|
# The hooks that let a module `extend`ed onto N reshape N's INSTANCE side (#1730): its singleton `extended` /
|
|
354
655
|
# `extend_object` run against N at the `extend` and may mix into N; its instance `included` /
|
|
355
656
|
# `append_features` / `prepended` / `prepend_features` / `inherited` become N's own singleton hooks and run
|
|
@@ -357,11 +658,44 @@ module Rigor
|
|
|
357
658
|
SINGLETON_EXTEND_HOOKS = %i[extended extend_object].freeze
|
|
358
659
|
INSTANCE_EXTEND_HOOKS = %i[included append_features prepended prepend_features inherited].freeze
|
|
359
660
|
|
|
360
|
-
|
|
661
|
+
# ADR-129 WD1 — three interning Memos, not run-scoped: a value is a frozen Array of frozen Strings built from the
|
|
662
|
+
# name and frozen constants alone, so an entry is fixed once written and holds nothing of a run. Each hit goes
|
|
663
|
+
# through `MemoAudit.check`, and an audit recomputation bypasses the table.
|
|
664
|
+
EXTEND_HOOK_KEYS = TypingState.memo(
|
|
665
|
+
:resolution_chain_extend_hook_keys,
|
|
666
|
+
audit: "extend hook keys",
|
|
667
|
+
reason: "keyed on the module name; a frozen Array of frozen Strings built from the name and the extend-hook " \
|
|
668
|
+
"constants alone"
|
|
669
|
+
)
|
|
670
|
+
HOOK_KEYS = TypingState.memo(
|
|
671
|
+
:resolution_chain_hook_keys,
|
|
672
|
+
audit: "hook keys",
|
|
673
|
+
reason: "keyed on the class name; a frozen Array of frozen Strings built from the name and " \
|
|
674
|
+
"Relevance::HOOKS alone"
|
|
675
|
+
)
|
|
676
|
+
EXTERNAL_KEYS = TypingState.memo(
|
|
677
|
+
:resolution_chain_external_keys,
|
|
678
|
+
audit: "external keys",
|
|
679
|
+
reason: "keyed on the class name; HOOK_KEYS' answer plus one frozen String built from the name"
|
|
680
|
+
)
|
|
681
|
+
private_constant :EXTEND_HOOK_KEYS, :HOOK_KEYS, :EXTERNAL_KEYS
|
|
361
682
|
|
|
362
683
|
# The frozen negative method keys of `name`'s {SINGLETON_EXTEND_HOOKS} and {INSTANCE_EXTEND_HOOKS}.
|
|
363
684
|
def self.extend_hook_keys(name)
|
|
364
|
-
|
|
685
|
+
return build_extend_hook_keys(name) if Inference::MemoAudit.enabled? && Inference::MemoAudit.suspended?
|
|
686
|
+
|
|
687
|
+
state = TypingState.current
|
|
688
|
+
table = (state[EXTEND_HOOK_KEYS] ||= {})
|
|
689
|
+
stored = table[name]
|
|
690
|
+
if stored.nil?
|
|
691
|
+
return table[name] = Inference::MemoAudit.key_audit("extend hook keys") { build_extend_hook_keys(name) }
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
Inference::MemoAudit.check("extend hook keys", stored, name) { build_extend_hook_keys(name) }
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def self.build_extend_hook_keys(name)
|
|
698
|
+
(
|
|
365
699
|
SINGLETON_EXTEND_HOOKS.map { |hook| "method:#{name}.#{hook}".freeze } +
|
|
366
700
|
INSTANCE_EXTEND_HOOKS.map { |hook| "method:#{name}##{hook}".freeze }
|
|
367
701
|
).freeze
|
|
@@ -385,7 +719,7 @@ module Rigor
|
|
|
385
719
|
end
|
|
386
720
|
|
|
387
721
|
# `[[spelling, resolved name or nil, candidate names], …]`: every module the project entries of an instance chain
|
|
388
|
-
# `extend`, as {Builder#extend_hook_taint
|
|
722
|
+
# `extend`, as {Builder#extend_hook_taint} resolved them.
|
|
389
723
|
def self.extend_targets(scope, flavor, entries)
|
|
390
724
|
resolver = resolver_for(scope, flavor)
|
|
391
725
|
extends = scope.discovery.discovered_extends
|
|
@@ -399,19 +733,37 @@ module Rigor
|
|
|
399
733
|
end.uniq.freeze
|
|
400
734
|
end
|
|
401
735
|
|
|
402
|
-
@hook_keys = {}
|
|
403
|
-
@external_keys = {}
|
|
404
|
-
|
|
405
736
|
# The frozen `method:Name.hook` negative keys of `name`'s {Relevance::HOOKS}, built once per name.
|
|
406
737
|
def self.hook_keys(name)
|
|
407
|
-
|
|
738
|
+
return build_hook_keys(name) if Inference::MemoAudit.enabled? && Inference::MemoAudit.suspended?
|
|
739
|
+
|
|
740
|
+
state = TypingState.current
|
|
741
|
+
table = (state[HOOK_KEYS] ||= {})
|
|
742
|
+
stored = table[name]
|
|
743
|
+
return table[name] = Inference::MemoAudit.key_audit("hook keys") { build_hook_keys(name) } if stored.nil?
|
|
744
|
+
|
|
745
|
+
Inference::MemoAudit.check("hook keys", stored, name) { build_hook_keys(name) }
|
|
408
746
|
end
|
|
409
747
|
|
|
748
|
+
def self.build_hook_keys(name) = Relevance::HOOKS.map { |hook| "method:#{name}.#{hook}".freeze }.freeze
|
|
749
|
+
|
|
410
750
|
# {hook_keys} plus the `mixin:<name>` key an external entry files for its unpositioned mixins (#1661).
|
|
411
751
|
def self.external_keys(name)
|
|
412
|
-
|
|
752
|
+
return build_external_keys(name) if Inference::MemoAudit.enabled? && Inference::MemoAudit.suspended?
|
|
753
|
+
|
|
754
|
+
state = TypingState.current
|
|
755
|
+
table = (state[EXTERNAL_KEYS] ||= {})
|
|
756
|
+
stored = table[name]
|
|
757
|
+
if stored.nil?
|
|
758
|
+
return table[name] = Inference::MemoAudit.key_audit("external keys") { build_external_keys(name) }
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
Inference::MemoAudit.check("external keys", stored, name) { build_external_keys(name) }
|
|
413
762
|
end
|
|
414
763
|
|
|
764
|
+
def self.build_external_keys(name) = (hook_keys(name) + ["mixin:#{name}".freeze]).freeze
|
|
765
|
+
private_class_method :build_extend_hook_keys, :build_hook_keys, :build_external_keys
|
|
766
|
+
|
|
415
767
|
def record_head(scope, start, side)
|
|
416
768
|
ResolutionChain.record_class(scope, @root) unless start.zero? && @entries.first&.name == @root
|
|
417
769
|
start.times { |index| ResolutionChain.record_entry(scope, @entries[index], side) }
|
|
@@ -443,6 +795,25 @@ module Rigor
|
|
|
443
795
|
@entries.index { |entry| entry.name == name && entry.side == side }
|
|
444
796
|
end
|
|
445
797
|
|
|
798
|
+
# Issue #2979 — the position of the first entry after `name`'s own that is a true ancestor of it, or nil when
|
|
799
|
+
# `name` has no entry on `side`. The entries of the names bound to the same class object
|
|
800
|
+
# (`K = Struct.new("Named", …)`) follow `name`'s own directly, and a `def` under one of them is a redefinition
|
|
801
|
+
# of the class's method, not an override, so a reader of "the entries after the class" starts past them.
|
|
802
|
+
def ancestors_start(scope, name, side = @side)
|
|
803
|
+
own = index_of(name, side)
|
|
804
|
+
return nil if own.nil?
|
|
805
|
+
|
|
806
|
+
twins = DiscoveryIndex.same_class_names(scope.discovery.discovered_parameter_envelopes[name])
|
|
807
|
+
start = own + 1
|
|
808
|
+
start += 1 while twins_entry?(@entries[start], twins, side)
|
|
809
|
+
start
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
def twins_entry?(entry, twins, side)
|
|
813
|
+
!twins.empty? && !entry.nil? && entry.side == side && twins.include?(entry.name)
|
|
814
|
+
end
|
|
815
|
+
private :twins_entry?
|
|
816
|
+
|
|
446
817
|
# A LEVEL is one superclass step: the class (or its singleton) with the modules inserted around it,
|
|
447
818
|
# before the next superclass's entries begin. `level_classes[k]` is that class, or nil for a level that
|
|
448
819
|
# is an external superclass. Only COMPLETE levels are counted — a level the {LIMIT} cut in half is not.
|
|
@@ -494,8 +865,21 @@ module Rigor
|
|
|
494
865
|
end
|
|
495
866
|
private :compute_levels
|
|
496
867
|
|
|
497
|
-
|
|
498
|
-
|
|
868
|
+
# ADR-129 WD1 — a run-scoped Memo: one `[discovery, flavors]` slot keyed on the discovery index's identity, the
|
|
869
|
+
# only input a chain reads.
|
|
870
|
+
#
|
|
871
|
+
# ADR-129 G2 — every table of the slot's flavor buckets is audited on a hit under `RIGOR_MEMO_AUDIT=1`, and an
|
|
872
|
+
# audit recomputation reads a fresh, unstored bucket ({.flavor_bucket}), so it reads nothing stored.
|
|
873
|
+
MEMO = TypingState.memo(
|
|
874
|
+
:resolution_chain,
|
|
875
|
+
run_scoped: true,
|
|
876
|
+
audit: ["resolution chain", "resolution chain name", "resolution chain relevance",
|
|
877
|
+
"resolution chain master order", "resolution chain linearisation", "resolution chain mark",
|
|
878
|
+
"resolution chain entry", "resolution chain external entry", "singleton hook verdict"],
|
|
879
|
+
reason: "one slot keyed on the discovery index's identity; a chain is a pure function of that frozen index " \
|
|
880
|
+
"and the flavor"
|
|
881
|
+
)
|
|
882
|
+
private_constant :MEMO
|
|
499
883
|
|
|
500
884
|
FLAVORS = %i[methods constants arity].freeze
|
|
501
885
|
|
|
@@ -507,17 +891,40 @@ module Rigor
|
|
|
507
891
|
|
|
508
892
|
bucket = flavor_bucket(scope.discovery, flavor)
|
|
509
893
|
chains = bucket[side]
|
|
510
|
-
|
|
894
|
+
cached = chains[class_name]
|
|
895
|
+
if cached.nil?
|
|
896
|
+
return chains[class_name] = Inference::MemoAudit.key_audit("resolution chain") do
|
|
897
|
+
Builder.new(scope, flavor, bucket).chain(class_name, side)
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
return cached unless Inference::MemoAudit.enabled?
|
|
901
|
+
|
|
902
|
+
Inference::MemoAudit.check("resolution chain", cached.audit_signature, class_name, side) do
|
|
903
|
+
fresh = Analysis::DependencyRecorder.withhold do
|
|
904
|
+
Builder.new(scope, flavor, flavor_bucket(scope.discovery, flavor)).chain(class_name, side)
|
|
905
|
+
end
|
|
906
|
+
fresh.first.audit_signature
|
|
907
|
+
end
|
|
908
|
+
cached
|
|
511
909
|
end
|
|
512
910
|
|
|
911
|
+
# The flavor's bucket of memo tables. While an audit recomputation runs ({Inference::MemoAudit.suspended?})
|
|
912
|
+
# every call answers a fresh bucket that is never stored, so the recomputation neither reads nor writes the
|
|
913
|
+
# slot.
|
|
513
914
|
def self.flavor_bucket(discovery, flavor)
|
|
514
|
-
|
|
915
|
+
return new_bucket if Inference::MemoAudit.enabled? && Inference::MemoAudit.suspended?
|
|
916
|
+
|
|
917
|
+
state = TypingState.current
|
|
918
|
+
slot = state[MEMO]
|
|
515
919
|
unless slot && slot[0].equal?(discovery)
|
|
516
920
|
slot = [discovery, {}]
|
|
517
|
-
|
|
921
|
+
state[MEMO] = slot
|
|
518
922
|
end
|
|
519
|
-
slot[1][flavor] ||=
|
|
520
|
-
|
|
923
|
+
slot[1][flavor] ||= new_bucket
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
def self.new_bucket
|
|
927
|
+
{ instance: {}, singleton: {}, names: {}, interned: {}, externals: {}, master: {}, marks: {} }
|
|
521
928
|
end
|
|
522
929
|
|
|
523
930
|
# Which project class an as-written ancestor name denotes, under one flavor — shared by the chain and
|
|
@@ -534,9 +941,13 @@ module Rigor
|
|
|
534
941
|
# ambiguous spelling names (`:arity` only), or nil.
|
|
535
942
|
def resolve(owner, raw)
|
|
536
943
|
by_owner = (@names[owner] ||= {})
|
|
537
|
-
|
|
944
|
+
unless by_owner.key?(raw)
|
|
945
|
+
return by_owner[raw] = Inference::MemoAudit.key_audit("resolution chain name") { compute(owner, raw) }
|
|
946
|
+
end
|
|
538
947
|
|
|
539
|
-
by_owner[raw]
|
|
948
|
+
Inference::MemoAudit.check("resolution chain name", by_owner[raw], owner, raw) do
|
|
949
|
+
Analysis::DependencyRecorder.withhold { compute(owner, raw) }.first
|
|
950
|
+
end
|
|
540
951
|
end
|
|
541
952
|
|
|
542
953
|
# {#resolve}, with an ambiguous spelling read as unresolved.
|
|
@@ -545,8 +956,28 @@ module Rigor
|
|
|
545
956
|
resolved.is_a?(String) ? resolved : nil
|
|
546
957
|
end
|
|
547
958
|
|
|
959
|
+
# Issue #1592 — {#resolve_one}, or else the first candidate the project declares at all: a module holding only
|
|
960
|
+
# hooks (`included do extend M end`) is no project class under any flavor, yet its hooks run.
|
|
961
|
+
# The search stops at the first candidate that exists in any form: one RBS or a gem declares and the project's
|
|
962
|
+
# source does not has no hooks the tables know, so it answers nil.
|
|
963
|
+
#
|
|
964
|
+
# Issue #3015 — a module whose bucket holds the concern entry counts too: its body runs `extend
|
|
965
|
+
# ActiveSupport::Concern`, so the project declares it. A one-file index has no `discovered_class_sources`, and
|
|
966
|
+
# would otherwise drop a concern whose only defs sit in its blocks from an includer's chain.
|
|
967
|
+
def resolve_declared(owner, raw)
|
|
968
|
+
candidates(owner, raw).each do |candidate|
|
|
969
|
+
return candidate if project?(candidate) || @discovery.discovered_class_sources.key?(candidate) ||
|
|
970
|
+
@discovery.discovered_parameter_envelopes[candidate]&.key?(CONCERN_KEY)
|
|
971
|
+
return nil if rbs_known?(candidate)
|
|
972
|
+
end
|
|
973
|
+
nil
|
|
974
|
+
end
|
|
975
|
+
|
|
548
976
|
def candidates(owner, raw) = @scope.ancestor_name_candidates(owner, raw)
|
|
549
977
|
|
|
978
|
+
# Whether `name`, already qualified, is a project class under this flavor.
|
|
979
|
+
def project_name?(name) = project?(name)
|
|
980
|
+
|
|
550
981
|
private
|
|
551
982
|
|
|
552
983
|
def compute(owner, raw)
|
|
@@ -557,6 +988,12 @@ module Rigor
|
|
|
557
988
|
alternatives.empty? ? nil : alternatives
|
|
558
989
|
end
|
|
559
990
|
|
|
991
|
+
def rbs_known?(name)
|
|
992
|
+
Rigor::Reflection.rbs_class_known?(name, scope: @scope)
|
|
993
|
+
rescue StandardError
|
|
994
|
+
false
|
|
995
|
+
end
|
|
996
|
+
|
|
560
997
|
def project?(name)
|
|
561
998
|
case @flavor
|
|
562
999
|
when :constants
|
|
@@ -572,7 +1009,7 @@ module Rigor
|
|
|
572
1009
|
end
|
|
573
1010
|
private_constant :Resolver
|
|
574
1011
|
|
|
575
|
-
private_class_method :flavor_bucket
|
|
1012
|
+
private_class_method :flavor_bucket, :new_bucket
|
|
576
1013
|
|
|
577
1014
|
# {Relevance}'s verdict memo, one table per flavor bucket.
|
|
578
1015
|
def self.relevance_memo(scope, flavor)
|
|
@@ -716,9 +1153,16 @@ module Rigor
|
|
|
716
1153
|
def self.master_memo(scope, flavor, key)
|
|
717
1154
|
bucket = flavor_bucket(scope.discovery, flavor)
|
|
718
1155
|
memo = bucket[:master]
|
|
719
|
-
|
|
1156
|
+
unless memo.key?(key)
|
|
1157
|
+
return memo[key] = Inference::MemoAudit.key_audit("resolution chain master order") do
|
|
1158
|
+
yield(Resolver.new(scope, flavor, bucket))
|
|
1159
|
+
end
|
|
1160
|
+
end
|
|
720
1161
|
|
|
721
|
-
memo[key]
|
|
1162
|
+
Inference::MemoAudit.check("resolution chain master order", memo[key], flavor, key) do
|
|
1163
|
+
fresh = flavor_bucket(scope.discovery, flavor)
|
|
1164
|
+
Analysis::DependencyRecorder.withhold { yield(Resolver.new(scope, flavor, fresh)) }.first
|
|
1165
|
+
end
|
|
722
1166
|
end
|
|
723
1167
|
|
|
724
1168
|
# The linearisation itself, in one world: Ruby's skip rule (`retro: false`), or every skipped insertion
|
|
@@ -768,7 +1212,7 @@ module Rigor
|
|
|
768
1212
|
# The chain for `root`, carrying its retro world when the skip rule skipped anything on the way.
|
|
769
1213
|
def chain(root, side)
|
|
770
1214
|
entries, starts, classes, forks, marks, retro_ok = lin_for(root, side)
|
|
771
|
-
order_marked = marks.any? { |mark| mark.kind
|
|
1215
|
+
order_marked = marks.any? { |mark| !HOOK_MARK_KINDS.include?(mark.kind) }
|
|
772
1216
|
retro = build_retro(root, side) if forks == 1 && retro_ok && !order_marked && !@retro
|
|
773
1217
|
cut_at = cut_position(entries)
|
|
774
1218
|
return whole_chain(root, side, entries, starts, classes, [forks, marks], retro) if cut_at.nil?
|
|
@@ -827,14 +1271,10 @@ module Rigor
|
|
|
827
1271
|
memoised(@retro ? :lin_singleton_retro : :lin_singleton, name, depth) { compute_singleton(name, depth) }
|
|
828
1272
|
end
|
|
829
1273
|
|
|
830
|
-
def memoised(table, name, depth)
|
|
1274
|
+
def memoised(table, name, depth, &)
|
|
831
1275
|
memo = (@bucket[table] ||= {})
|
|
832
1276
|
cached = memo[name]
|
|
833
|
-
if cached
|
|
834
|
-
# A memoised chain's forks are the asker's too.
|
|
835
|
-
@frames.last&.absorb(cached[3], cached[4], cached[5])
|
|
836
|
-
return cached
|
|
837
|
-
end
|
|
1277
|
+
return absorb_cached(audited_lin(cached, table, name)) if cached
|
|
838
1278
|
|
|
839
1279
|
# A cycle (`class Foo < Foo` resolved inside its own namespace) or a hierarchy deeper than the budget
|
|
840
1280
|
# contributes nothing past that point, and the chains computed while it did are not memoised: they
|
|
@@ -849,7 +1289,7 @@ module Rigor
|
|
|
849
1289
|
frame = Frame.new(0, false, NO_MARKS, true)
|
|
850
1290
|
@frames.push(frame)
|
|
851
1291
|
stack.push(name)
|
|
852
|
-
entries, starts, classes =
|
|
1292
|
+
entries, starts, classes = Inference::MemoAudit.key_audit("resolution chain linearisation", &)
|
|
853
1293
|
stack.pop
|
|
854
1294
|
@frames.pop
|
|
855
1295
|
lin = [entries, starts, classes, frame.forks, frame.marks, frame.retro_ok].freeze
|
|
@@ -858,6 +1298,29 @@ module Rigor
|
|
|
858
1298
|
lin
|
|
859
1299
|
end
|
|
860
1300
|
|
|
1301
|
+
# A memoised linearisation rebuilt for the audit by a Builder over a fresh bucket. A memoised one met no
|
|
1302
|
+
# cycle and stayed inside the depth budget, so its depth does not change it; a retro world that fit
|
|
1303
|
+
# {LIMIT} once fits it again.
|
|
1304
|
+
# A memoised chain's forks are the asker's too.
|
|
1305
|
+
def absorb_cached(cached)
|
|
1306
|
+
@frames.last&.absorb(cached[3], cached[4], cached[5])
|
|
1307
|
+
cached
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
def audited_lin(cached, table, name)
|
|
1311
|
+
Inference::MemoAudit.check("resolution chain linearisation", cached, table, name) { fresh_lin(table, name) }
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
SINGLETON_LIN_TABLES = %i[lin_singleton lin_singleton_retro].freeze
|
|
1315
|
+
private_constant :SINGLETON_LIN_TABLES
|
|
1316
|
+
|
|
1317
|
+
def fresh_lin(table, name)
|
|
1318
|
+
side = SINGLETON_LIN_TABLES.include?(table) ? :singleton : :instance
|
|
1319
|
+
builder = Builder.new(@scope, @flavor, ResolutionChain.send(:flavor_bucket, @discovery, @flavor),
|
|
1320
|
+
retro: @retro)
|
|
1321
|
+
Analysis::DependencyRecorder.withhold { builder.send(:lin_for, name, side) }.first
|
|
1322
|
+
end
|
|
1323
|
+
|
|
861
1324
|
def hand_up(frame)
|
|
862
1325
|
parent = @frames.last
|
|
863
1326
|
return if parent.nil?
|
|
@@ -867,9 +1330,11 @@ module Rigor
|
|
|
867
1330
|
end
|
|
868
1331
|
|
|
869
1332
|
def compute_instance(name, depth)
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1333
|
+
twins = same_class_twins(name)
|
|
1334
|
+
mark_instance_node(name, :include)
|
|
1335
|
+
twins.each { |owner| mark_instance_node(owner, :include) }
|
|
1336
|
+
return same_class_instance(name, twins, depth) unless twins.empty?
|
|
1337
|
+
|
|
873
1338
|
entries = [project_entry(name, :instance)]
|
|
874
1339
|
super_lin = superclass_lin(name, :instance, depth)
|
|
875
1340
|
entries.concat(super_lin[0])
|
|
@@ -881,12 +1346,333 @@ module Rigor
|
|
|
881
1346
|
end
|
|
882
1347
|
|
|
883
1348
|
def compute_singleton(name, depth)
|
|
884
|
-
|
|
1349
|
+
twins = same_class_twins(name)
|
|
1350
|
+
own = @discovery.discovered_extends[name] || EMPTY
|
|
1351
|
+
own_prepends = own_singleton_prepends(name, own)
|
|
1352
|
+
mark_unsettled(name, :extend, singleton_order_edges(own, own_prepends))
|
|
1353
|
+
twins.each { |owner| mark_unsettled(owner, :extend, (@discovery.discovered_extends[owner] || EMPTY).size) }
|
|
1354
|
+
return same_class_singleton(name, twins, depth) unless twins.empty?
|
|
1355
|
+
|
|
885
1356
|
entries = [project_entry(name, :singleton)]
|
|
886
1357
|
super_lin = superclass_lin(name, :singleton, depth)
|
|
887
1358
|
entries.concat(super_lin[0])
|
|
888
|
-
|
|
889
|
-
|
|
1359
|
+
own_extends = own_prepends.empty? ? own : own - own_prepends
|
|
1360
|
+
extends, prepends = singleton_mixins(name, own_extends, own_prepends)
|
|
1361
|
+
boundary = prepends.empty? ? CLASS_FIRST : prepend_all(entries, name, prepends, depth)
|
|
1362
|
+
super_start = include_all(entries, name, extends, EMPTY, depth, boundary)
|
|
1363
|
+
finish(entries, name, super_lin, super_start)
|
|
1364
|
+
end
|
|
1365
|
+
|
|
1366
|
+
# #1592 — the class's own `extend`s and the modules its direct mixins' hooks extend it with
|
|
1367
|
+
# ({#hook_extensions}), nearest first. Ruby inserts each where its statement ran, and the tables keep the
|
|
1368
|
+
# `extend` and `include` statements apart, so with both kinds present the two blocks' order is a fork: the
|
|
1369
|
+
# chain puts the own `extend`s nearer (the usual layout writes them after the `include`s) and the retro world
|
|
1370
|
+
# the hooks' modules, and a reader that the two worlds answer alike stands, since then only one block can
|
|
1371
|
+
# answer the name. An order the hooks do not fix, or an include whose own position is not a fact, is two
|
|
1372
|
+
# forks, which settle to master. Answers `[extends, prepends]`: a concern's `ClassMethods` on a prepend edge is
|
|
1373
|
+
# prepended to the class object (`Concern#prepend_features` runs `base.singleton_class.prepend`), so it sits
|
|
1374
|
+
# ahead of the class object's own entry.
|
|
1375
|
+
#
|
|
1376
|
+
# Issue #2998 — `own_prepends` are the class's own singleton-side prepends ({#own_singleton_prepends}), which
|
|
1377
|
+
# sit ahead of the class object too. An `extend` always sits behind the class object and a prepend always
|
|
1378
|
+
# ahead of it, so only two prepends can trade places ({#order_prepends}).
|
|
1379
|
+
def singleton_mixins(name, extends, own_prepends)
|
|
1380
|
+
plan = hook_extensions(name)
|
|
1381
|
+
if plan.open || unnamed_instance_mixin?(name)
|
|
1382
|
+
@frames.last.add_marks(mark_for(name, :include_hook, UNNAMED_LIST, false))
|
|
1383
|
+
end
|
|
1384
|
+
fork_hook_order(name, 2) unless plan.ordered || (plan.extends.empty? && plan.prepends.empty?)
|
|
1385
|
+
prepends = order_prepends(name, own_prepends, plan.prepends)
|
|
1386
|
+
hooked = plan.extends
|
|
1387
|
+
return [extends, prepends] if hooked.empty?
|
|
1388
|
+
return [hooked, prepends] if extends.empty?
|
|
1389
|
+
|
|
1390
|
+
fork_hook_order(name, 1)
|
|
1391
|
+
[@retro ? hooked + extends : extends + hooked, prepends]
|
|
1392
|
+
end
|
|
1393
|
+
|
|
1394
|
+
# The class object's prepends, nearest first, once its own are placed against its hooks'. Where their order
|
|
1395
|
+
# is not a fact (own and hook prepends both, or two own ones from two files or with an edge whose position is
|
|
1396
|
+
# not a fact), the chain counts two forks, which settle to master, and the class takes a `:prepend_order`
|
|
1397
|
+
# mark: the typing read declines a definer from its prepend region, since neither order is Ruby's for sure.
|
|
1398
|
+
def order_prepends(name, own, hooked)
|
|
1399
|
+
return hooked if own.empty?
|
|
1400
|
+
|
|
1401
|
+
if !hooked.empty? || (own.size > 1 && !own_prepends_ordered?(name))
|
|
1402
|
+
@frames.last.forks += 2
|
|
1403
|
+
@frames.last.add_marks(mark_for(name, :prepend_order, nil, false))
|
|
1404
|
+
end
|
|
1405
|
+
own + hooked
|
|
1406
|
+
end
|
|
1407
|
+
|
|
1408
|
+
def own_prepends_ordered?(name)
|
|
1409
|
+
!multi_file?(name) && (@discovery.unpositioned_mixins[name]&.dig(:extend) || EMPTY).empty?
|
|
1410
|
+
end
|
|
1411
|
+
|
|
1412
|
+
# Issue #2998 — the spellings of `name`'s singleton edges (`discovered_extends`) that are prepends: a
|
|
1413
|
+
# `class << self; prepend P` or a `singleton_class.prepend P` the walk filed as a
|
|
1414
|
+
# `[ENVELOPE_SINGLETON_PREPEND_KIND, "P"]` entry. They keep the table's order, nearest first, which is the
|
|
1415
|
+
# prepends' own: a later `prepend` sits ahead of an earlier one. A class with any takes the `:singleton_prepend`
|
|
1416
|
+
# mark, which tells the typing read that master's order, which knows no singleton prepend, may answer
|
|
1417
|
+
# otherwise.
|
|
1418
|
+
#
|
|
1419
|
+
# Issue #3020 — a prepend whose module no table names (`class << self; prepend(*MODS)`,
|
|
1420
|
+
# `singleton_class.prepend(*MODS)`) files the `"?"` entry instead, which no edge spells: the class takes the
|
|
1421
|
+
# `:unnamed_prepend` mark, and the typing read declines a definer at or behind the class object.
|
|
1422
|
+
def own_singleton_prepends(name, own)
|
|
1423
|
+
bucket = @discovery.discovered_parameter_envelopes[name]
|
|
1424
|
+
return EMPTY if bucket.nil?
|
|
1425
|
+
|
|
1426
|
+
@frames.last.add_marks(mark_for(name, :unnamed_prepend, nil, false)) if bucket.key?(UNNAMED_PREPEND_KEY)
|
|
1427
|
+
return EMPTY if own.empty?
|
|
1428
|
+
|
|
1429
|
+
prepends = own.select { |raw| bucket.key?([DiscoveryIndex::ENVELOPE_SINGLETON_PREPEND_KIND, raw]) }
|
|
1430
|
+
return EMPTY if prepends.empty?
|
|
1431
|
+
|
|
1432
|
+
@frames.last.add_marks(mark_for(name, :singleton_prepend, nil, false))
|
|
1433
|
+
prepends
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1436
|
+
# The edge count the multi-file rule weighs: a prepend sits ahead of the class object and an `extend` behind
|
|
1437
|
+
# it whatever the load order, so only edges of one kind can trade places across files.
|
|
1438
|
+
def singleton_order_edges(own, own_prepends)
|
|
1439
|
+
return own.size if own_prepends.empty?
|
|
1440
|
+
|
|
1441
|
+
[own_prepends.size, own.size - own_prepends.size].max
|
|
1442
|
+
end
|
|
1443
|
+
|
|
1444
|
+
def fork_hook_order(name, weight)
|
|
1445
|
+
@frames.last.forks += weight
|
|
1446
|
+
@frames.last.add_marks(mark_for(name, :hook_order, nil, false))
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1449
|
+
# The {HookPlan} for a class: the `[owner, spelling]` pairs of the modules the hooks of its direct
|
|
1450
|
+
# mixins extend it with, nearest first (a later include's ahead of an earlier one's, and within one mixin the
|
|
1451
|
+
# hook's own extends ahead of a concern's `ClassMethods`, which `append_features` extends before the
|
|
1452
|
+
# `included` block runs); the concern `ClassMethods` its prepend edges prepend to its singleton; whether one of
|
|
1453
|
+
# those hooks may extend it with a module no entry names; and whether the order is a fact. A concern includer
|
|
1454
|
+
# gets nothing from a concern: `Concern#append_features` records it as a dependency instead (#2992).
|
|
1455
|
+
def hook_extensions(name)
|
|
1456
|
+
includes = @discovery.discovered_includes[name] || EMPTY
|
|
1457
|
+
return NO_HOOKS if includes.empty?
|
|
1458
|
+
|
|
1459
|
+
listed = @discovery.unpositioned_mixins[name]&.dig(:include) || EMPTY
|
|
1460
|
+
plan = HookPlan.new([], [], false, true)
|
|
1461
|
+
each_hooked_mixin(name, includes) do |raw, mixin, hooks, loose|
|
|
1462
|
+
unpositioned = loose || listed.include?(raw) || listed.include?(WILDCARD_LIST.first)
|
|
1463
|
+
merge_targets(plan, hook_targets(mixin, hooks), unpositioned)
|
|
1464
|
+
plan.open ||= uncertain_dependencies?(mixin)
|
|
1465
|
+
end
|
|
1466
|
+
# Two files' includes run in load order.
|
|
1467
|
+
plan.ordered &&= plan.extends.size + plan.prepends.size < 2 || !multi_file?(name)
|
|
1468
|
+
plan
|
|
1469
|
+
end
|
|
1470
|
+
|
|
1471
|
+
# Adds one mixin's targets to the class's plan; an include whose own position is not a fact (`unpositioned`)
|
|
1472
|
+
# leaves its modules' order unknown.
|
|
1473
|
+
def merge_targets(plan, targets, unpositioned)
|
|
1474
|
+
plan.open ||= targets.open
|
|
1475
|
+
return if targets.extends.empty? && targets.prepends.empty?
|
|
1476
|
+
|
|
1477
|
+
plan.ordered &&= targets.ordered && !unpositioned
|
|
1478
|
+
plan.extends.concat(targets.extends)
|
|
1479
|
+
plan.prepends.concat(targets.prepends)
|
|
1480
|
+
end
|
|
1481
|
+
|
|
1482
|
+
# Issue #2994 — whether a module no table names may be mixed into `name`'s instance side by a call whose target
|
|
1483
|
+
# is `name` itself, so that its `included` / `prepended` hook runs with `name` as `base` and may extend the
|
|
1484
|
+
# class object with anything: `name` lists {Relevance::UNNAMED} on its own instance side (`include(*MODS)`,
|
|
1485
|
+
# `include helper`), or a module it includes does (a hook's `base.include(*MODS)`; that module's own
|
|
1486
|
+
# `include(*MODS)` is read so too). A superclass's own mark is read when its singleton chain is built. The
|
|
1487
|
+
# entries this reads are the class's own sites and its direct mixins', which a singleton chain's readers file
|
|
1488
|
+
# already. An extended module's hook that splats a module in already declines the read on another route.
|
|
1489
|
+
def unnamed_instance_mixin?(name)
|
|
1490
|
+
return true if lists_unnamed?(name)
|
|
1491
|
+
|
|
1492
|
+
(@discovery.discovered_includes[name] || EMPTY).uniq.any? do |raw|
|
|
1493
|
+
mixin = @resolver.resolve_declared(name, raw)
|
|
1494
|
+
!mixin.nil? && lists_unnamed?(mixin)
|
|
1495
|
+
end
|
|
1496
|
+
end
|
|
1497
|
+
|
|
1498
|
+
def lists_unnamed?(name)
|
|
1499
|
+
listed = @discovery.unpositioned_mixins[name]&.dig(:include)
|
|
1500
|
+
!listed.nil? && listed.include?(UNNAMED_LIST.last)
|
|
1501
|
+
end
|
|
1502
|
+
|
|
1503
|
+
HookPlan = Struct.new(:extends, :prepends, :open, :ordered)
|
|
1504
|
+
private_constant :HookPlan
|
|
1505
|
+
|
|
1506
|
+
# Yields each direct mixin of `name`, nearest first, as `[spelling, resolved, hooks]`: the hooks its edges
|
|
1507
|
+
# run, `:included` for an `include` and `:prepended` for a `prepend`.
|
|
1508
|
+
#
|
|
1509
|
+
# Issue #2992 — a concern mixin's dependencies follow it, each on the same edge
|
|
1510
|
+
# ({ResolutionChain.concern_dependencies}): `Concern#append_features` includes them into `name` ahead of the
|
|
1511
|
+
# concern itself, so their modules sit farther than the concern's. `spelling` is the mixin's for each, and
|
|
1512
|
+
# `loose` says a dependency's place is not a fact: `Concern` puts a prepended dependency ahead of the included
|
|
1513
|
+
# ones, which the tables list the other way, and two files of the concern add theirs in load order.
|
|
1514
|
+
def each_hooked_mixin(name, includes)
|
|
1515
|
+
prepends = @discovery.discovered_prepends[name] || EMPTY
|
|
1516
|
+
concern = concern?(name)
|
|
1517
|
+
includes.uniq.each do |raw|
|
|
1518
|
+
mixin = @resolver.resolve_declared(name, raw)
|
|
1519
|
+
next if mixin.nil? || (concern && concern?(mixin))
|
|
1520
|
+
|
|
1521
|
+
hooks = []
|
|
1522
|
+
hooks << :prepended if prepends.include?(raw)
|
|
1523
|
+
hooks << :included if includes.count(raw) > prepends.count(raw)
|
|
1524
|
+
yield raw, mixin, hooks, false
|
|
1525
|
+
dependencies = ResolutionChain.concern_dependencies(@discovery, @resolver, mixin)
|
|
1526
|
+
loose = dependencies.size > 1 && dependencies.any? { |member| loose_dependencies?(member, mixin) }
|
|
1527
|
+
dependencies.each { |dependency| yield raw, dependency, hooks, loose }
|
|
1528
|
+
end
|
|
1529
|
+
end
|
|
1530
|
+
|
|
1531
|
+
# Issue #2992 — whether a concern declared in two files includes a concern its dependency entries do not name:
|
|
1532
|
+
# written in a reopening without the `extend ActiveSupport::Concern`, that `include` is a dependency when the
|
|
1533
|
+
# reopening loads after the `extend` and an ordinary include of the concern itself when it loads first, so the
|
|
1534
|
+
# includer may or may not be extended with that concern's modules.
|
|
1535
|
+
def uncertain_dependencies?(mixin)
|
|
1536
|
+
return false unless concern?(mixin) && multi_file?(mixin)
|
|
1537
|
+
|
|
1538
|
+
spellings = ResolutionChain.dependency_spellings(@discovery, mixin)
|
|
1539
|
+
(@discovery.discovered_includes[mixin] || EMPTY).any? do |raw|
|
|
1540
|
+
next false if spellings.include?(raw)
|
|
1541
|
+
|
|
1542
|
+
dependency = @resolver.resolve_declared(mixin, raw)
|
|
1543
|
+
!dependency.nil? && concern?(dependency)
|
|
1544
|
+
end
|
|
1545
|
+
end
|
|
1546
|
+
|
|
1547
|
+
# Whether the concern `owner` or its dependency `member` prepends a module or is declared in two files.
|
|
1548
|
+
def loose_dependencies?(member, owner)
|
|
1549
|
+
[member, owner].any? { |name| multi_file?(name) || !(@discovery.discovered_prepends[name] || EMPTY).empty? }
|
|
1550
|
+
end
|
|
1551
|
+
|
|
1552
|
+
NO_HOOKS = HookPlan.new(EMPTY, EMPTY, false, true).freeze
|
|
1553
|
+
private_constant :NO_HOOKS
|
|
1554
|
+
|
|
1555
|
+
def concern?(name)
|
|
1556
|
+
bucket = @discovery.discovered_parameter_envelopes[name]
|
|
1557
|
+
!bucket.nil? && bucket.key?(CONCERN_KEY)
|
|
1558
|
+
end
|
|
1559
|
+
|
|
1560
|
+
# One mixin's hook-extended modules for the edges `hooks` runs, as {#hook_extensions} answers them.
|
|
1561
|
+
# A hook defined twice (`ENVELOPE_HOOK_DEF_KIND`) is open: which definition Ruby keeps is load order.
|
|
1562
|
+
#
|
|
1563
|
+
# Issue #3005 — a hook block's `def self.x`s (`<included>`) sit on the class object itself, ahead of every
|
|
1564
|
+
# module the block extends it with, so that module goes first and does not make the order unknown.
|
|
1565
|
+
def hook_targets(mixin, hooks)
|
|
1566
|
+
named, open = hook_keys(mixin, hooks)
|
|
1567
|
+
blocks, modules = named.partition { |raw| DiscoveryIndex::HOOK_BLOCK_SEGMENTS.include?(raw) }
|
|
1568
|
+
pairs = (blocks + modules).map { |raw| hook_target_pair(mixin, raw) }
|
|
1569
|
+
targets = HookPlan.new(pairs, [], open, modules.size <= 1)
|
|
1570
|
+
found = concern?(mixin) && const_get_target(mixin, "ClassMethods")
|
|
1571
|
+
(hooks.include?(:prepended) ? targets.prepends : targets.extends) << [mixin, "::#{found}"] if found
|
|
1572
|
+
targets
|
|
1573
|
+
end
|
|
1574
|
+
|
|
1575
|
+
# `[named, open]`: the spellings the mixin's hooks for `hooks` extend with, and whether one of them is open
|
|
1576
|
+
# or defined twice.
|
|
1577
|
+
def hook_keys(mixin, hooks)
|
|
1578
|
+
named = []
|
|
1579
|
+
open = false
|
|
1580
|
+
defs = Hash.new(0)
|
|
1581
|
+
(@discovery.discovered_parameter_envelopes[mixin] || EMPTY_BUCKET).each_key do |key|
|
|
1582
|
+
next unless hooks.include?(ResolutionChain.effective_hook(@discovery, mixin, ResolutionChain.hook_of(key)))
|
|
1583
|
+
|
|
1584
|
+
case key.first
|
|
1585
|
+
when DiscoveryIndex::ENVELOPE_HOOK_EXTEND_KIND then named << key[1]
|
|
1586
|
+
when DiscoveryIndex::ENVELOPE_HOOK_EXTEND_OPEN_KIND then open = true
|
|
1587
|
+
else defs[key[1]] += 1
|
|
1588
|
+
end
|
|
1589
|
+
end
|
|
1590
|
+
[named.uniq, open || defs.each_value.any? { |count| count > 1 }]
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
EMPTY_BUCKET = {}.freeze
|
|
1594
|
+
private_constant :EMPTY_BUCKET
|
|
1595
|
+
|
|
1596
|
+
# The module a hook's constant names, written in the mixin's body: `Mixin::Raw` first (the mixin is the
|
|
1597
|
+
# innermost cref there), then the names its header's nesting gives. A name no project module answers stays
|
|
1598
|
+
# the mixin's spelling, an external entry.
|
|
1599
|
+
def hook_target_pair(mixin, raw)
|
|
1600
|
+
own = "#{mixin}::#{raw}"
|
|
1601
|
+
return [mixin, "::#{own}"] if !raw.start_with?("::") && @resolver.project_name?(own)
|
|
1602
|
+
|
|
1603
|
+
[mixin, raw]
|
|
1604
|
+
end
|
|
1605
|
+
|
|
1606
|
+
# `mixin.const_get(raw)`, as `Concern#append_features` looks `ClassMethods` up: `Name::Raw` for each entry of
|
|
1607
|
+
# the mixin's own instance chain, then a top-level `Raw`; nil when no project module answers, which Concern
|
|
1608
|
+
# then does not extend.
|
|
1609
|
+
#
|
|
1610
|
+
# Issue #2992 — a concern the mixin depends on is no ancestor of it ({ResolutionChain.concern_dependencies}),
|
|
1611
|
+
# so its `ClassMethods` is not found here: it reaches the includer as that dependency's own.
|
|
1612
|
+
def const_get_target(mixin, raw)
|
|
1613
|
+
chain = ResolutionChain.for(@scope, mixin, :instance, @flavor)
|
|
1614
|
+
dependencies = ResolutionChain.concern_dependencies(@discovery, @resolver, mixin)
|
|
1615
|
+
chain.entries.each do |entry|
|
|
1616
|
+
next if dependencies.include?(entry.name)
|
|
1617
|
+
|
|
1618
|
+
names = entry.external? ? entry.candidates : [entry.name]
|
|
1619
|
+
found = names.map { |owner| [owner, "#{owner}::#{raw}"] }.find do |owner, candidate|
|
|
1620
|
+
@resolver.project_name?(candidate) && ResolutionChain.const_get_candidate?(@discovery, owner, candidate)
|
|
1621
|
+
end&.last
|
|
1622
|
+
return found if found
|
|
1623
|
+
end
|
|
1624
|
+
@resolver.project_name?(raw) ? raw : nil
|
|
1625
|
+
end
|
|
1626
|
+
|
|
1627
|
+
# The instance-side marks of one node; `kind` is the side's mixin kind, `:include`. `discovered_includes`
|
|
1628
|
+
# already carries the prepended names, so it counts every instance-side edge once.
|
|
1629
|
+
def mark_instance_node(name, kind)
|
|
1630
|
+
mark_unsettled(name, kind, (@discovery.discovered_includes[name] || EMPTY).size)
|
|
1631
|
+
hook = extend_hook_taint(name)
|
|
1632
|
+
@frames.last.add_marks(mark_for(name, :extend_hook, hook, false)) if hook
|
|
1633
|
+
end
|
|
1634
|
+
|
|
1635
|
+
# Issue #2979 — the names `name`'s envelope bucket binds to the same class object (`K = Struct.new("Named",
|
|
1636
|
+
# …)` makes `K` and `Struct::Named` one class), without `name`; empty, and allocating nothing, for nearly every
|
|
1637
|
+
# class. The chain of either name heads with every name's entry, so a reader asks each name's own tables where
|
|
1638
|
+
# Ruby asks the one class's; nothing is copied between the names. Only the direct binding counts: two
|
|
1639
|
+
# `Struct.new("Named", …)` writes make two classes in Ruby, and neither constant reaches the other's tables
|
|
1640
|
+
# through `Struct::Named`. {ResolutionChain#ancestors_start} tells a reader where the true ancestors begin.
|
|
1641
|
+
def same_class_twins(name)
|
|
1642
|
+
twins = DiscoveryIndex.same_class_names(@discovery.discovered_parameter_envelopes[name])
|
|
1643
|
+
twins.include?(name) ? twins - [name] : twins
|
|
1644
|
+
end
|
|
1645
|
+
|
|
1646
|
+
# The instance side of a class with twins: every name's prepends ahead of the class, then every name's
|
|
1647
|
+
# includes after the LAST name, so the names stay ahead of every module either one includes, as the one class
|
|
1648
|
+
# heads its own ancestors. The superclass edge stays `name`'s own: a Struct's class has none the tables
|
|
1649
|
+
# record, under either name.
|
|
1650
|
+
def same_class_instance(name, twins, depth)
|
|
1651
|
+
names = [name, *twins].uniq
|
|
1652
|
+
entries = names.map { |owner| project_entry(owner, :instance) }
|
|
1653
|
+
super_lin = superclass_lin(name, :instance, depth)
|
|
1654
|
+
entries.concat(super_lin[0])
|
|
1655
|
+
boundary = [0, names.size]
|
|
1656
|
+
names.each do |owner|
|
|
1657
|
+
boundary = prepend_all(entries, owner, @discovery.discovered_prepends[owner] || EMPTY, depth, boundary)
|
|
1658
|
+
end
|
|
1659
|
+
super_start = names.reduce(boundary[1]) do |start, owner|
|
|
1660
|
+
include_all(entries, owner, @discovery.discovered_includes[owner] || EMPTY,
|
|
1661
|
+
@discovery.discovered_prepends[owner] || EMPTY, depth, [boundary[0] + names.size - 1, start])
|
|
1662
|
+
end
|
|
1663
|
+
finish(entries, name, super_lin, super_start)
|
|
1664
|
+
end
|
|
1665
|
+
|
|
1666
|
+
# The singleton twin of {#same_class_instance}: every name's class-object entry, then every name's extends.
|
|
1667
|
+
def same_class_singleton(name, twins, depth)
|
|
1668
|
+
names = [name, *twins].uniq
|
|
1669
|
+
entries = names.map { |owner| project_entry(owner, :singleton) }
|
|
1670
|
+
super_lin = superclass_lin(name, :singleton, depth)
|
|
1671
|
+
entries.concat(super_lin[0])
|
|
1672
|
+
super_start = names.reduce(names.size) do |start, owner|
|
|
1673
|
+
include_all(entries, owner, @discovery.discovered_extends[owner] || EMPTY, EMPTY, depth,
|
|
1674
|
+
[names.size - 1, start])
|
|
1675
|
+
end
|
|
890
1676
|
finish(entries, name, super_lin, super_start)
|
|
891
1677
|
end
|
|
892
1678
|
|
|
@@ -908,24 +1694,53 @@ module Rigor
|
|
|
908
1694
|
# The one-element frozen array carrying the {Mark} of `[name, kind]`, interned in the bucket.
|
|
909
1695
|
def mark_for(name, kind, listed, multi)
|
|
910
1696
|
by_node = (@bucket[:marks][kind] ||= {})
|
|
911
|
-
by_node[name]
|
|
912
|
-
|
|
1697
|
+
cached = by_node[name]
|
|
1698
|
+
if cached.nil?
|
|
1699
|
+
return by_node[name] = Inference::MemoAudit.key_audit("resolution chain mark") do
|
|
1700
|
+
new_mark(name, kind, listed, multi)
|
|
1701
|
+
end
|
|
1702
|
+
end
|
|
1703
|
+
return cached unless Inference::MemoAudit.enabled?
|
|
1704
|
+
|
|
1705
|
+
# The failure names the whole key: the bucket's flavor, the mark's kind, the node.
|
|
1706
|
+
Inference::MemoAudit.check("resolution chain mark", cached, [@flavor, kind], name) do
|
|
1707
|
+
new_mark(name, kind, listed, multi)
|
|
1708
|
+
end
|
|
913
1709
|
end
|
|
914
1710
|
|
|
915
|
-
|
|
916
|
-
|
|
1711
|
+
def new_mark(name, kind, listed, multi)
|
|
1712
|
+
[ResolutionChain::Mark.new(name, kind, listed.nil? ? EMPTY : listed.to_a.freeze, multi)].freeze
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
# The `[origin, super_start]` of a one-name chain: the class at 0, its superclass's entries from 1.
|
|
1716
|
+
CLASS_FIRST = [0, 1].freeze
|
|
1717
|
+
private_constant :CLASS_FIRST
|
|
917
1718
|
|
|
918
1719
|
# #1730 — whether a module `name` extends has a hook that may mix into `name`'s instance side or its
|
|
919
1720
|
# includers' ({ResolutionChain.extend_reshapes_instance?}). Ruby runs it at the `extend`, so the mixin is
|
|
920
1721
|
# not a fact of the tables and `name`'s instance side is unpositioned. A spelling no project module
|
|
921
1722
|
# answers taints nothing: the project cannot see its hooks.
|
|
922
|
-
|
|
923
|
-
|
|
1723
|
+
#
|
|
1724
|
+
# The mark's list: nil when no extended module taints, else {UNNAMED_LIST} when one of those modules lists `"?"`
|
|
1725
|
+
# (#2991), {WILDCARD_LIST} otherwise. Only the marker carries over: a name such a module lists is spelled
|
|
1726
|
+
# relative to that module, not to the extender.
|
|
1727
|
+
def extend_hook_taint(name)
|
|
1728
|
+
found = nil
|
|
1729
|
+
(@discovery.discovered_extends[name] || EMPTY).each do |raw|
|
|
924
1730
|
resolved = @resolver.resolve_one(name, raw)
|
|
925
|
-
|
|
1731
|
+
next if resolved.nil? || !ResolutionChain.extend_reshapes_instance?(@scope, resolved)
|
|
1732
|
+
return UNNAMED_LIST if @discovery.unpositioned_mixins[resolved][:include].include?(UNNAMED_LIST.last)
|
|
1733
|
+
|
|
1734
|
+
found = WILDCARD_LIST
|
|
926
1735
|
end
|
|
1736
|
+
found
|
|
927
1737
|
end
|
|
928
1738
|
|
|
1739
|
+
WILDCARD_LIST = ["*"].freeze # Relevance::WILDCARD, which loads after this file
|
|
1740
|
+
UNNAMED_LIST = ["*", "?"].freeze # with Relevance::UNNAMED
|
|
1741
|
+
UNNAMED_PREPEND_KEY = [DiscoveryIndex::ENVELOPE_SINGLETON_PREPEND_KIND, "?"].freeze # with Relevance::UNNAMED
|
|
1742
|
+
private_constant :WILDCARD_LIST, :UNNAMED_LIST, :UNNAMED_PREPEND_KEY
|
|
1743
|
+
|
|
929
1744
|
def multi_file?(name)
|
|
930
1745
|
sites = @discovery.discovered_class_sources[name]
|
|
931
1746
|
!sites.nil? && sites.size >= 2
|
|
@@ -970,14 +1785,15 @@ module Rigor
|
|
|
970
1785
|
# module's own includes may have run before or after), and the tables record `include M; prepend M`
|
|
971
1786
|
# exactly as a plain `prepend M`. A direct prepend of the module itself is always inserted, in Ruby and
|
|
972
1787
|
# here, so it forks nothing.
|
|
973
|
-
def prepend_all(entries, owner, prepends, depth)
|
|
974
|
-
origin =
|
|
975
|
-
super_start = 1
|
|
1788
|
+
def prepend_all(entries, owner, prepends, depth, boundary = CLASS_FIRST)
|
|
1789
|
+
origin, super_start = boundary
|
|
976
1790
|
# The table holds every statement, nearest first: a same-owner repeat is a no-op in Ruby (the first
|
|
977
1791
|
# statement wins), so it is dropped here rather than met as a fork.
|
|
978
1792
|
prepends.reverse.uniq.each do |raw|
|
|
979
|
-
|
|
980
|
-
|
|
1793
|
+
# A hook's module comes as `[owner, spelling]`, as in {#include_all}.
|
|
1794
|
+
from, raw = raw.is_a?(Array) ? raw : [owner, raw]
|
|
1795
|
+
modules = Array(resolve(from, raw))
|
|
1796
|
+
each_mixin_chain(from, raw, depth) do |sub|
|
|
981
1797
|
point = -1
|
|
982
1798
|
sub.each do |entry|
|
|
983
1799
|
found = entries.index(entry)
|
|
@@ -1008,7 +1824,9 @@ module Rigor
|
|
|
1008
1824
|
mixins.reverse_each do |raw|
|
|
1009
1825
|
next if skip.include?(raw)
|
|
1010
1826
|
|
|
1011
|
-
|
|
1827
|
+
# A hook's module comes as `[owner, spelling]`: the mixin whose body wrote it resolves it.
|
|
1828
|
+
from, raw = raw.is_a?(Array) ? raw : [owner, raw]
|
|
1829
|
+
each_mixin_chain(from, raw, depth) do |sub|
|
|
1012
1830
|
point = origin
|
|
1013
1831
|
sub.each_with_index do |entry, index|
|
|
1014
1832
|
found = @retro ? nil : entries.index(entry)
|
|
@@ -1049,14 +1867,43 @@ module Rigor
|
|
|
1049
1867
|
|
|
1050
1868
|
def project_entry(name, side)
|
|
1051
1869
|
by_side = (@bucket[:interned][side] ||= {})
|
|
1052
|
-
by_side[name]
|
|
1870
|
+
cached = by_side[name]
|
|
1871
|
+
if cached.nil?
|
|
1872
|
+
return by_side[name] = Inference::MemoAudit.key_audit("resolution chain entry") do
|
|
1873
|
+
new_project_entry(name, side)
|
|
1874
|
+
end
|
|
1875
|
+
end
|
|
1876
|
+
return cached unless Inference::MemoAudit.enabled?
|
|
1877
|
+
|
|
1878
|
+
Inference::MemoAudit.check("resolution chain entry", cached, [@flavor, side], name) do
|
|
1879
|
+
new_project_entry(name, side)
|
|
1880
|
+
end
|
|
1881
|
+
end
|
|
1882
|
+
|
|
1883
|
+
def new_project_entry(name, side)
|
|
1884
|
+
Entry.new(name, side, nil, nil, false, name.to_s.split("::").last.freeze, nil)
|
|
1053
1885
|
end
|
|
1054
1886
|
|
|
1055
1887
|
def external_entry(owner, raw, side, superclass_edge)
|
|
1056
1888
|
by_edge = ((@bucket[:externals][side] ||= {})[superclass_edge] ||= {})
|
|
1057
1889
|
by_owner = (by_edge[owner] ||= {})
|
|
1058
|
-
by_owner[raw]
|
|
1059
|
-
|
|
1890
|
+
cached = by_owner[raw]
|
|
1891
|
+
if cached.nil?
|
|
1892
|
+
return by_owner[raw] = Inference::MemoAudit.key_audit("resolution chain external entry") do
|
|
1893
|
+
new_external_entry(owner, raw, side, superclass_edge)
|
|
1894
|
+
end
|
|
1895
|
+
end
|
|
1896
|
+
return cached unless Inference::MemoAudit.enabled?
|
|
1897
|
+
|
|
1898
|
+
# The failure names the whole key: flavor, side, superclass edge, owner and spelling.
|
|
1899
|
+
Inference::MemoAudit.check("resolution chain external entry", cached, [@flavor, side, superclass_edge],
|
|
1900
|
+
[owner, raw]) do
|
|
1901
|
+
Analysis::DependencyRecorder.withhold { new_external_entry(owner, raw, side, superclass_edge) }.first
|
|
1902
|
+
end
|
|
1903
|
+
end
|
|
1904
|
+
|
|
1905
|
+
def new_external_entry(owner, raw, side, superclass_edge)
|
|
1906
|
+
Entry.new(nil, side, raw, @scope.ancestor_name_candidates(owner, raw).freeze, superclass_edge, nil, owner)
|
|
1060
1907
|
end
|
|
1061
1908
|
end
|
|
1062
1909
|
private_constant :Builder
|