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,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../source/node_children"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
module Analysis
|
|
5
7
|
# Issues #665 / #674 / #683 / #686 / #696 — the ONE definition of "an internal failure made this run
|
|
@@ -104,12 +106,52 @@ module Rigor
|
|
|
104
106
|
# into. Built here, not at either rescue site, so the two twins cannot drift (issue #665) — and so
|
|
105
107
|
# the appended crash frame is derived identically on the sequential and pooled paths. Keeps the
|
|
106
108
|
# {CHECK_RULE_MESSAGE_PREFIX} prefix every consumer matches on; the frame is a trailing hint.
|
|
107
|
-
def check_rule_message(error)
|
|
109
|
+
def check_rule_message(error, root = nil)
|
|
110
|
+
return stack_overflow_message(error, root) if error.is_a?(SystemStackError)
|
|
111
|
+
|
|
108
112
|
base = "#{CHECK_RULE_MESSAGE_PREFIX}: #{error.class}: #{error.message}"
|
|
109
113
|
frame = crash_frame(error)
|
|
110
114
|
frame ? "#{base} (#{frame})" : base
|
|
111
115
|
end
|
|
112
116
|
|
|
117
|
+
# The crash-row message for a rescued error, for the per-file rescues of `Runner` and `WorkerSession`. Re-raises a
|
|
118
|
+
# `SystemStackError` raised from C (no backtrace): that one leaves no stack to recover on and a second would kill
|
|
119
|
+
# the process with a signal, so it keeps the run-level abort ({CLI::FATAL_ERRORS}, exit 70). `parse_result` may be
|
|
120
|
+
# nil when the error predates the parse.
|
|
121
|
+
def contained_message!(error, parse_result)
|
|
122
|
+
raise error if error.is_a?(SystemStackError) && error.backtrace.nil?
|
|
123
|
+
|
|
124
|
+
check_rule_message(error, parse_result&.value)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# #2455 — the AST depth above which a `SystemStackError` is read as the file's own nesting rather than a Rigor
|
|
128
|
+
# recursion defect. The recursive walkers overflow from about 700 nested `if`s (twice as many AST levels) and
|
|
129
|
+
# 1,300 nested array literals or chained calls, so a file past this bound is one they cannot be expected to walk.
|
|
130
|
+
DEEP_NESTING_BOUND = 500
|
|
131
|
+
|
|
132
|
+
DEEP_NESTING_DETAIL = "this file is nested too deeply to analyse (stack level too deep); " \
|
|
133
|
+
"split the deeply nested expression"
|
|
134
|
+
STACK_DEFECT_DETAIL = "stack level too deep; this is a defect in Rigor, please report it at " \
|
|
135
|
+
"https://github.com/rigortype/rigor/issues"
|
|
136
|
+
|
|
137
|
+
# No crash frame: the frame that overflowed is whichever the stack ran out in, which differs by mode and cache and
|
|
138
|
+
# would make the row unstable. `root` is nil when the file never parsed, which reads as the defect wording.
|
|
139
|
+
def stack_overflow_message(error, root)
|
|
140
|
+
deep = !root.nil? && Source::NodeChildren.nested_deeper_than?(root, DEEP_NESTING_BOUND)
|
|
141
|
+
"#{CHECK_RULE_MESSAGE_PREFIX}: #{error.class}: #{deep ? DEEP_NESTING_DETAIL : STACK_DEFECT_DETAIL}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# ADR-129 WD5 (#2785) — `diagnostics` (one file's) with a crash row for each of `errors`, raised errors the
|
|
145
|
+
# typing path may have swallowed under a `rescue StandardError` (`MemoAudit`'s mismatches), so a swallowed one
|
|
146
|
+
# is reported exactly as one no rescue caught. An error that did reach the file's crash row is not repeated.
|
|
147
|
+
def with_swallowed(path, diagnostics, errors)
|
|
148
|
+
return diagnostics if errors.empty?
|
|
149
|
+
|
|
150
|
+
seen = diagnostics.map(&:message)
|
|
151
|
+
rows = errors.map { |error| check_rule_message(error) }.uniq.reject { |message| seen.include?(message) }
|
|
152
|
+
diagnostics + rows.map { |message| Diagnostic.new(path: path, line: 1, column: 1, message: message) }
|
|
153
|
+
end
|
|
154
|
+
|
|
113
155
|
# The first `lib/rigor/` backtrace frame — the raise site, path made repo-relative so it reads the
|
|
114
156
|
# same whether Rigor runs from a checkout or an installed gem. A LOCATION, not an attribution: a
|
|
115
157
|
# bundled or third-party plugin also lives under `lib/rigor/<plugin>/`, so a frame here says where
|
|
@@ -149,8 +191,8 @@ module Rigor
|
|
|
149
191
|
|
|
150
192
|
# True when `diagnostic` reports a failure inside Rigor that left the run readable but invalid as a
|
|
151
193
|
# measurement of Rigor — see {ANALYZER_DEFECT_RULES}. Orthogonal to {.discards_file_analysis?}: the
|
|
152
|
-
# user-facing tier reads the run; the Rigor-measuring tier should refuse it (the mutation fuzz
|
|
153
|
-
#
|
|
194
|
+
# user-facing tier reads the run; the Rigor-measuring tier should refuse it (the mutation fuzz and the
|
|
195
|
+
# ADR-69 kill oracle both do, #790).
|
|
154
196
|
def analyzer_defect?(diagnostic)
|
|
155
197
|
ANALYZER_DEFECT_RULES.include?(diagnostic.rule)
|
|
156
198
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../typing_state"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
module Analysis
|
|
5
7
|
# ADR-46 slice 1 — records, per analyzed file, which OTHER source files its analysis read declarations /
|
|
@@ -12,9 +14,67 @@ module Rigor
|
|
|
12
14
|
#
|
|
13
15
|
# Modelled on {Inference::BudgetTrace}: process-thread-local state, a cheap disabled fast path, and a
|
|
14
16
|
# frozen snapshot for consumers.
|
|
15
|
-
module DependencyRecorder
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
module DependencyRecorder # rubocop:disable Metrics/ModuleLength
|
|
18
|
+
# ADR-129 WD1 — the installed accumulator is a Frame whose contents are a Resource for its suspension count, the
|
|
19
|
+
# one mutation that touches ({record_for}); its filings are entries of the bound Table {READS} and are journaled
|
|
20
|
+
# instead. WD4 shuts the `type_of` memo under recording only until these reads are journaled, which they now are.
|
|
21
|
+
# The activation count below is a process switch, outside the typing state (WD5).
|
|
22
|
+
RECORDER = TypingState.frame(
|
|
23
|
+
:dependency_recorder,
|
|
24
|
+
contents: :resource, reason: "the consumer's accumulator a recording installs; each read files into it",
|
|
25
|
+
# G1′ witness: the dependency reads are a journaled Table (WD4), so the memo may run under recording, and typing
|
|
26
|
+
# reads the accumulator (`read_site`, `read_name`) inside `type_of`.
|
|
27
|
+
witness: "spec/fixtures/typing_state_witnesses/dependency_recorder.rb"
|
|
28
|
+
)
|
|
29
|
+
private_constant :RECORDER
|
|
30
|
+
|
|
31
|
+
# ADR-129 WD1 — the recorded dependency reads are a bound Table: the installed {Accumulator} and each open
|
|
32
|
+
# {Capture} are its stores, keyed on the event tuples they deduplicate on (a frozen `[path, symbol_or_nil]` pair
|
|
33
|
+
# for a positive edge, a `"kind:name"` String for a name-keyed one). Each event that files into a store logs a
|
|
34
|
+
# read of its key (`true` when the store already held it, else {TypingState::ABSENT}) and, when it adds, a write
|
|
35
|
+
# of `true`, while a capture is open, so a later `type_of` memo can validate the reads and replay the writes.
|
|
36
|
+
# The keys are built only then: a run with no journal capture open allocates nothing for the journal, whether
|
|
37
|
+
# or not an ADR-84 capture is open.
|
|
38
|
+
# ADR-129 WD3 — {READS}'s entry protocol over an {Accumulator} or a {Capture}: a positive edge's
|
|
39
|
+
# `[path, symbol_or_nil]` pair is held in the accumulator's symbol or ancestry table (a capture's `reads`), a
|
|
40
|
+
# name-keyed `"kind:name"` String in `missing`. Every entry is `true`; a replayed write files the edge the way the
|
|
41
|
+
# event that logged it did.
|
|
42
|
+
module Entries
|
|
43
|
+
module_function
|
|
44
|
+
|
|
45
|
+
def entry(store, key)
|
|
46
|
+
held = if key.is_a?(String) then store.missing.include?(key)
|
|
47
|
+
elsif store.is_a?(Capture) then store.reads.include?(key)
|
|
48
|
+
else store.holds_read?(key[0], key[1])
|
|
49
|
+
end
|
|
50
|
+
held ? true : TypingState::ABSENT
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def put(store, key, _value)
|
|
54
|
+
if key.is_a?(String) then store.missing.add(key)
|
|
55
|
+
elsif store.is_a?(Capture) then store.reads.add(key)
|
|
56
|
+
else store.add_read(key[0], key[1])
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete(store, key)
|
|
61
|
+
if key.is_a?(String) then store.missing.delete(key)
|
|
62
|
+
elsif store.is_a?(Capture) then store.reads.delete(key)
|
|
63
|
+
else store.remove_read(key[0], key[1])
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def container(_state, _slot)
|
|
68
|
+
raise ArgumentError, "dependency reads are a bound table: a store is an accumulator or a capture"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
READS = TypingState.table(
|
|
73
|
+
:dependency_reads,
|
|
74
|
+
bound: true, entries: Entries,
|
|
75
|
+
reason: "event tuple => true per accumulator and per open capture; set-only, in first-filing order"
|
|
76
|
+
)
|
|
77
|
+
private_constant :READS
|
|
18
78
|
|
|
19
79
|
# ADR-84 WD2 — thread-local Array of active {Capture}s (nil when none). A capture observes-and-forwards:
|
|
20
80
|
# every read recorded while it is active flows into the current consumer's accumulator exactly as before
|
|
@@ -27,14 +87,24 @@ module Rigor
|
|
|
27
87
|
# each outer one's: the inner capture was pushed after the outer, and everything added since reached both.
|
|
28
88
|
# {withhold} swaps in a fresh stack and restores this one untouched, so it keeps the property too.
|
|
29
89
|
# {tee_read} and {tee_missing} rely on it to stop at the first capture that already holds an event.
|
|
30
|
-
|
|
31
|
-
|
|
90
|
+
#
|
|
91
|
+
# ADR-129 WD3 — the stack is an id-determined Resource: every change of it ({capture}'s push and pop,
|
|
92
|
+
# {withhold}'s swap and restore) takes a fresh state id, so reading it on the event paths is exempt.
|
|
93
|
+
CAPTURES = TypingState.resource(
|
|
94
|
+
:dependency_capture_stack,
|
|
95
|
+
read_exemption: :id_determined,
|
|
96
|
+
reason: "the open capture windows, pushed per window and swapped by withhold"
|
|
97
|
+
)
|
|
98
|
+
private_constant :CAPTURES
|
|
32
99
|
|
|
33
100
|
# The read sets an {Accumulator} or {Capture} already holds in full, by identity: replayed into it, or
|
|
34
101
|
# captured by a window it saw every event of ({capture}). Both only ever grow and a {ReadSet} is frozen,
|
|
35
102
|
# so replaying such a set into the same target again adds nothing. Recording replays the same stored
|
|
36
103
|
# read set many times over: on Rigor's own `lib`, 71% of the replays into a consumer repeated one
|
|
37
104
|
# already made into it, carrying 65% of the replayed events.
|
|
105
|
+
#
|
|
106
|
+
# ADR-129 — the held marks are an extensional dedupe cache over the store's own {READS} entries (every event of
|
|
107
|
+
# a held set is already in the store), so marking and probing them log nothing; {ChainMemory} likewise.
|
|
38
108
|
module ReplayMemory
|
|
39
109
|
def holds?(read_set)
|
|
40
110
|
!@held.nil? && @held.key?(read_set)
|
|
@@ -74,8 +144,8 @@ module Rigor
|
|
|
74
144
|
@sources = Set.new
|
|
75
145
|
@missing = Set.new
|
|
76
146
|
# ADR-46 slice 4 — symbol-granularity tracking.
|
|
77
|
-
# `symbol_sources`: source_path →
|
|
78
|
-
# `ancestry_sources`:
|
|
147
|
+
# `symbol_sources`: source_path → the "ClassName#method" names read from it, for method-call deps.
|
|
148
|
+
# `ancestry_sources`: the source paths read for class-ancestry (superclass / include) deps —
|
|
79
149
|
# file-granularity by nature (a superclass edge touches the whole class).
|
|
80
150
|
@symbol_sources = Hash.new { |h, k| h[k] = Set.new }
|
|
81
151
|
@ancestry_sources = Set.new
|
|
@@ -90,6 +160,39 @@ module Rigor
|
|
|
90
160
|
@suspensions += 1
|
|
91
161
|
end
|
|
92
162
|
|
|
163
|
+
# The positive-edge aggregation shared by {DependencyRecorder.read_site}, {DependencyRecorder.replay} and a
|
|
164
|
+
# replayed {READS} write. `sources` is the union of the other two tables' paths, and this is the only writer of
|
|
165
|
+
# all three, so a read the finer table already holds is already in `sources` and costs one Set probe instead
|
|
166
|
+
# of two. Answers whether the read was new (truthy) or already held (nil), which is its {READS} filing.
|
|
167
|
+
def add_read(path, symbol)
|
|
168
|
+
added = symbol ? @symbol_sources[path].add?(symbol) : @ancestry_sources.add?(path)
|
|
169
|
+
@sources << path if added
|
|
170
|
+
added
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Undoes an {#add_read} that filed a new edge (G1's stage-4 form): `sources` keeps `path` while another table
|
|
174
|
+
# still holds it, and a `symbol_sources` set left empty goes, as no read had made it.
|
|
175
|
+
def remove_read(path, symbol)
|
|
176
|
+
if symbol
|
|
177
|
+
symbols = @symbol_sources.key?(path) ? @symbol_sources[path] : nil
|
|
178
|
+
if symbols
|
|
179
|
+
symbols.delete(symbol)
|
|
180
|
+
@symbol_sources.delete(path) if symbols.empty?
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
@ancestry_sources.delete(path)
|
|
184
|
+
end
|
|
185
|
+
@sources.delete(path) unless @ancestry_sources.include?(path) || @symbol_sources.key?(path)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Whether the edge {#add_read} files for `path` and `symbol` is held. Reads `symbol_sources` without its
|
|
189
|
+
# default block, so asking adds no empty set.
|
|
190
|
+
def holds_read?(path, symbol)
|
|
191
|
+
return @ancestry_sources.include?(path) unless symbol
|
|
192
|
+
|
|
193
|
+
@symbol_sources.key?(path) && @symbol_sources[path].include?(symbol)
|
|
194
|
+
end
|
|
195
|
+
|
|
93
196
|
def snapshot
|
|
94
197
|
frozen_sym = @symbol_sources.transform_values(&:freeze).freeze
|
|
95
198
|
Record.new(
|
|
@@ -103,8 +206,8 @@ module Rigor
|
|
|
103
206
|
end
|
|
104
207
|
|
|
105
208
|
# Frozen record of one file's cross-file reads.
|
|
106
|
-
# `symbol_sources`: source_path → frozen
|
|
107
|
-
# `ancestry_sources`: frozen
|
|
209
|
+
# `symbol_sources`: source_path → frozen set of "ClassName#method" names (method-call edges).
|
|
210
|
+
# `ancestry_sources`: frozen set of source paths (class-ancestry edges, file-granularity).
|
|
108
211
|
Record = Data.define(:consumer, :sources, :missing, :symbol_sources, :ancestry_sources)
|
|
109
212
|
|
|
110
213
|
# ADR-84 WD2 — a replayable, consumer-independent read-set: `reads` is a frozen Set of frozen
|
|
@@ -154,16 +257,22 @@ module Rigor
|
|
|
154
257
|
# the frozen {Record}. Nests safely (the inner consumer's reads do not leak to the outer one); restores
|
|
155
258
|
# the previous recorder on exit.
|
|
156
259
|
def record_for(consumer)
|
|
157
|
-
|
|
158
|
-
previous
|
|
260
|
+
state = TypingState.current
|
|
261
|
+
previous = state[RECORDER]
|
|
262
|
+
if previous
|
|
263
|
+
state.touch(RECORDER)
|
|
264
|
+
previous.suspend!
|
|
265
|
+
end
|
|
159
266
|
accumulator = Accumulator.new(consumer.to_s)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
267
|
+
state.with(RECORDER, accumulator) do
|
|
268
|
+
@mutex.synchronize { @active_count += 1 }
|
|
269
|
+
begin
|
|
270
|
+
yield
|
|
271
|
+
accumulator.snapshot
|
|
272
|
+
ensure
|
|
273
|
+
@mutex.synchronize { @active_count -= 1 }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
167
276
|
end
|
|
168
277
|
|
|
169
278
|
# Plain integer read (GVL-atomic) — no `Thread.current` lookup on the disabled fast path.
|
|
@@ -175,25 +284,26 @@ module Rigor
|
|
|
175
284
|
# window. {active?} answers for the process and is the cheap gate; this is the exact one, for a caller
|
|
176
285
|
# that must know whether an empty {capture} means "nothing to record" or "nothing was watching".
|
|
177
286
|
def recording?
|
|
178
|
-
!
|
|
287
|
+
!TypingState.current[RECORDER].nil?
|
|
179
288
|
end
|
|
180
289
|
|
|
181
290
|
# ADR-84 WD2 — runs the block under an observe-and-forward capture window and returns
|
|
182
291
|
# `[block_result, read_set]`. Reads recorded during the window reach the current consumer's accumulator
|
|
183
292
|
# unchanged (forwarding) and are additionally collected into the returned frozen {ReadSet} (observing),
|
|
184
|
-
# which {replay} can later apply to a different consumer. Nests (see
|
|
293
|
+
# which {replay} can later apply to a different consumer. Nests (see CAPTURES). Callers guard on
|
|
185
294
|
# {active?}; opening a capture with no accumulator active would collect reads no run is recording.
|
|
186
295
|
def capture
|
|
187
|
-
|
|
188
|
-
accumulator =
|
|
296
|
+
state = TypingState.current
|
|
297
|
+
accumulator = state[RECORDER]
|
|
189
298
|
suspensions = accumulator&.suspensions
|
|
190
299
|
capture = Capture.new
|
|
191
|
-
|
|
300
|
+
state.push(CAPTURES, capture)
|
|
192
301
|
begin
|
|
193
302
|
result = yield
|
|
194
303
|
ensure
|
|
195
|
-
|
|
304
|
+
state.pop(CAPTURES)
|
|
196
305
|
end
|
|
306
|
+
captures = state[CAPTURES]
|
|
197
307
|
read_set = capture.snapshot
|
|
198
308
|
note_captured(read_set, captures, accumulator, suspensions)
|
|
199
309
|
[result, read_set]
|
|
@@ -209,7 +319,7 @@ module Rigor
|
|
|
209
319
|
return if read_set.equal?(EMPTY_READ_SET)
|
|
210
320
|
|
|
211
321
|
captures.last&.hold!(read_set)
|
|
212
|
-
return unless accumulator &&
|
|
322
|
+
return unless accumulator && TypingState.current[RECORDER].equal?(accumulator)
|
|
213
323
|
return unless accumulator.suspensions == suspensions
|
|
214
324
|
|
|
215
325
|
accumulator.hold!(read_set)
|
|
@@ -223,18 +333,22 @@ module Rigor
|
|
|
223
333
|
# edge would make every correct call on a project class re-check on any edit to the class's files.
|
|
224
334
|
# Returns a nil read set when no recording is active.
|
|
225
335
|
def withhold
|
|
226
|
-
|
|
336
|
+
state = TypingState.current
|
|
337
|
+
previous = state[RECORDER]
|
|
227
338
|
return [yield, nil] if previous.nil?
|
|
228
339
|
|
|
229
|
-
previous_captures =
|
|
340
|
+
previous_captures = state[CAPTURES]
|
|
230
341
|
capture = Capture.new
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
342
|
+
result = state.with(RECORDER, Accumulator.new(previous.consumer)) do
|
|
343
|
+
# A swap of the whole stack: a write, then a rewrite, so the swapped-in stack is read under a fresh id.
|
|
344
|
+
state.write(CAPTURES, [capture])
|
|
345
|
+
state.rewrite(CAPTURES)
|
|
346
|
+
begin
|
|
347
|
+
yield
|
|
348
|
+
ensure
|
|
349
|
+
state.write(CAPTURES, previous_captures)
|
|
350
|
+
state.rewrite(CAPTURES)
|
|
351
|
+
end
|
|
238
352
|
end
|
|
239
353
|
[result, capture.snapshot]
|
|
240
354
|
end
|
|
@@ -243,11 +357,12 @@ module Rigor
|
|
|
243
357
|
# here, applying the same per-consumer self-read filter as {read_site}, and tees into every active
|
|
244
358
|
# capture so an enclosing capture window stays transitive when a memo hit substitutes for a body walk.
|
|
245
359
|
def replay(read_set)
|
|
246
|
-
|
|
360
|
+
state = TypingState.current
|
|
361
|
+
accumulator = state[RECORDER]
|
|
247
362
|
return if accumulator.nil? || read_set.nil? || read_set.equal?(EMPTY_READ_SET)
|
|
248
363
|
|
|
249
|
-
captures =
|
|
250
|
-
tee_read_set(captures, read_set) if captures && !captures.empty?
|
|
364
|
+
captures = state[CAPTURES]
|
|
365
|
+
tee_read_set(state, captures, read_set) if captures && !captures.empty?
|
|
251
366
|
# Each set keeps its own insertion order whichever target is filled first, and a skipped replay is one
|
|
252
367
|
# that could only have re-added what the target already holds.
|
|
253
368
|
return if accumulator.holds?(read_set)
|
|
@@ -255,25 +370,29 @@ module Rigor
|
|
|
255
370
|
consumer = accumulator.consumer
|
|
256
371
|
read_set.reads.each do |pair|
|
|
257
372
|
path, symbol = pair
|
|
258
|
-
|
|
373
|
+
next if path == consumer
|
|
374
|
+
|
|
375
|
+
added = accumulate_read(accumulator, path, symbol)
|
|
376
|
+
log_filing(state, accumulator, pair, added) if state.capturing?
|
|
259
377
|
end
|
|
260
378
|
missing = accumulator.missing
|
|
261
|
-
read_set.missing.each { |entry| missing
|
|
379
|
+
read_set.missing.each { |entry| file_missing(state, accumulator, missing, entry) }
|
|
262
380
|
accumulator.hold!(read_set)
|
|
263
381
|
end
|
|
264
382
|
|
|
265
383
|
# #1590 — runs the block, which files a resolution chain's edges from entry `start` (nil: the root and the
|
|
266
384
|
# whole chain) against `discovery`, unless the current consumer's accumulator AND the innermost open capture
|
|
267
385
|
# already hold that filing; the block files the same edges every time, so skipping it there changes nothing.
|
|
268
|
-
# Every event reaches every capture open at the time (see
|
|
386
|
+
# Every event reaches every capture open at the time (see CAPTURES), so the innermost capture holding the
|
|
269
387
|
# filing means every open capture does. A filing the accumulator holds but a capture opened since does not is
|
|
270
388
|
# filed again, so that capture's read set, replayed elsewhere, still carries it; and {withhold} starts a fresh
|
|
271
389
|
# accumulator and capture, which hold nothing. The marks are made only once the block has run, as {hold!}'s are.
|
|
272
390
|
def file_chain_once(chain, start, discovery)
|
|
273
|
-
|
|
391
|
+
state = TypingState.current
|
|
392
|
+
accumulator = state[RECORDER]
|
|
274
393
|
return if accumulator.nil?
|
|
275
394
|
|
|
276
|
-
captures =
|
|
395
|
+
captures = state[CAPTURES]
|
|
277
396
|
open = captures && !captures.empty?
|
|
278
397
|
if accumulator.chain_filed?(chain, start, discovery) &&
|
|
279
398
|
(!open || captures.last.chain_filed?(chain, start, discovery))
|
|
@@ -286,33 +405,78 @@ module Rigor
|
|
|
286
405
|
end
|
|
287
406
|
|
|
288
407
|
# {replay}'s share for the open captures. The innermost capture having seen `read_set` already means every
|
|
289
|
-
# open capture holds all of it (see
|
|
290
|
-
def tee_read_set(captures, read_set)
|
|
408
|
+
# open capture holds all of it (see CAPTURES).
|
|
409
|
+
def tee_read_set(state, captures, read_set)
|
|
291
410
|
innermost = captures.last
|
|
292
411
|
return if innermost.holds?(read_set)
|
|
293
412
|
|
|
294
|
-
read_set.reads.each { |pair| tee_read(captures, pair) }
|
|
295
|
-
read_set.missing.each { |entry| tee_missing(captures, entry) }
|
|
413
|
+
read_set.reads.each { |pair| tee_read(state, captures, pair) }
|
|
414
|
+
read_set.missing.each { |entry| tee_missing(state, captures, entry) }
|
|
296
415
|
innermost.hold!(read_set)
|
|
297
416
|
end
|
|
298
417
|
private_class_method :tee_read_set
|
|
299
418
|
|
|
300
419
|
# Adds a read pair to the open captures, innermost first, and stops at the first capture that already holds
|
|
301
|
-
# it: every capture outside that one holds it too (see
|
|
302
|
-
# adding it to every capture would leave them, in the same insertion order.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
420
|
+
# it: every capture outside that one holds it too (see CAPTURES). The capture sets end up exactly as
|
|
421
|
+
# adding it to every capture would leave them, in the same insertion order. Each capture probed logs its
|
|
422
|
+
# filing; the ones outside the first holder are not probed, and hold the key by the same property.
|
|
423
|
+
def tee_read(state, captures, pair)
|
|
424
|
+
if state.capturing?
|
|
425
|
+
tee_logged(state, captures, pair, :reads)
|
|
426
|
+
else
|
|
427
|
+
index = captures.size - 1
|
|
428
|
+
index -= 1 while index >= 0 && captures[index].reads.add?(pair)
|
|
429
|
+
end
|
|
306
430
|
end
|
|
307
431
|
private_class_method :tee_read
|
|
308
432
|
|
|
309
433
|
# {tee_read} for a name-keyed `missing` entry.
|
|
310
|
-
def tee_missing(captures, entry)
|
|
311
|
-
|
|
312
|
-
|
|
434
|
+
def tee_missing(state, captures, entry)
|
|
435
|
+
if state.capturing?
|
|
436
|
+
tee_logged(state, captures, entry, :missing)
|
|
437
|
+
else
|
|
438
|
+
index = captures.size - 1
|
|
439
|
+
index -= 1 while index >= 0 && captures[index].missing.add?(entry)
|
|
440
|
+
end
|
|
313
441
|
end
|
|
314
442
|
private_class_method :tee_missing
|
|
315
443
|
|
|
444
|
+
# {tee_read} / {tee_missing} while a capture is open: the same walk, logging each capture's filing.
|
|
445
|
+
def tee_logged(state, captures, key, set)
|
|
446
|
+
index = captures.size - 1
|
|
447
|
+
while index >= 0
|
|
448
|
+
capture = captures[index]
|
|
449
|
+
added = capture.public_send(set).add?(key)
|
|
450
|
+
log_filing(state, capture, key, added)
|
|
451
|
+
break unless added
|
|
452
|
+
|
|
453
|
+
index -= 1
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
private_class_method :tee_logged
|
|
457
|
+
|
|
458
|
+
# Logs one filing of `key` into `store` (an {Accumulator} or a {Capture}): a read of the key, which the store held
|
|
459
|
+
# unless the filing `added` it, and the write when it did. Callers log only while a capture is open.
|
|
460
|
+
def log_filing(state, store, key, added)
|
|
461
|
+
state.log_bound_read(READS, store, key, added ? TypingState::ABSENT : true)
|
|
462
|
+
state.log_bound_write(READS, store, key, true, TypingState::ABSENT) if added
|
|
463
|
+
end
|
|
464
|
+
private_class_method :log_filing
|
|
465
|
+
|
|
466
|
+
# A positive edge filed into the consumer's accumulator ({Accumulator#add_read}): truthy when it was new.
|
|
467
|
+
def accumulate_read(accumulator, path, symbol) = accumulator.add_read(path, symbol)
|
|
468
|
+
private_class_method :accumulate_read
|
|
469
|
+
|
|
470
|
+
# Files a name-keyed entry into an accumulator's `missing` set, logging the filing while a capture is open.
|
|
471
|
+
def file_missing(state, accumulator, missing, entry)
|
|
472
|
+
if state.capturing?
|
|
473
|
+
log_filing(state, accumulator, entry, missing.add?(entry))
|
|
474
|
+
else
|
|
475
|
+
missing << entry
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
private_class_method :file_missing
|
|
479
|
+
|
|
316
480
|
# Records that the current consumer read a declaration / body whose definition site is `path_line` (a
|
|
317
481
|
# `"path:line"` String, or nil). When `symbol` is given (a `"ClassName#method"` String), the read is a
|
|
318
482
|
# method-call edge and is recorded at symbol granularity in `symbol_sources` in addition to the coarse
|
|
@@ -321,17 +485,20 @@ module Rigor
|
|
|
321
485
|
# (ADR-84 WD2) observe every valid-path read UNfiltered — the self-read filter is per-consumer and is
|
|
322
486
|
# re-applied by {replay} against whichever consumer a replay serves.
|
|
323
487
|
def read_site(path_line, symbol = nil)
|
|
324
|
-
|
|
488
|
+
state = TypingState.current
|
|
489
|
+
accumulator = state[RECORDER]
|
|
325
490
|
return if accumulator.nil? || path_line.nil?
|
|
326
491
|
|
|
327
492
|
path = site_path(path_line)
|
|
328
493
|
return unless path
|
|
329
494
|
|
|
330
|
-
captures =
|
|
331
|
-
|
|
495
|
+
captures = state[CAPTURES]
|
|
496
|
+
pair = [path, symbol].freeze if (captures && !captures.empty?) || state.capturing?
|
|
497
|
+
tee_read(state, captures, pair) if captures && !captures.empty?
|
|
332
498
|
return if path == accumulator.consumer
|
|
333
499
|
|
|
334
|
-
accumulate_read(accumulator, path, symbol)
|
|
500
|
+
added = accumulate_read(accumulator, path, symbol)
|
|
501
|
+
log_filing(state, accumulator, pair, added) if state.capturing?
|
|
335
502
|
end
|
|
336
503
|
|
|
337
504
|
# The path part of a `"path:line"` site: what `path_line.split(":", 2).first` answers, without the Array
|
|
@@ -355,13 +522,14 @@ module Rigor
|
|
|
355
522
|
# currently wins: a second declarer appearing in a file the reader has no edge to must drop the name to
|
|
356
523
|
# `Dynamic` at that reader, and only a name-keyed edge can see it.
|
|
357
524
|
def read_name(kind, name)
|
|
358
|
-
|
|
525
|
+
state = TypingState.current
|
|
526
|
+
accumulator = state[RECORDER]
|
|
359
527
|
return if accumulator.nil?
|
|
360
528
|
|
|
361
529
|
entry = "#{kind}:#{name}"
|
|
362
|
-
captures =
|
|
363
|
-
tee_missing(captures, entry) if captures && !captures.empty?
|
|
364
|
-
accumulator.missing
|
|
530
|
+
captures = state[CAPTURES]
|
|
531
|
+
tee_missing(state, captures, entry) if captures && !captures.empty?
|
|
532
|
+
file_missing(state, accumulator, accumulator.missing, entry)
|
|
365
533
|
end
|
|
366
534
|
|
|
367
535
|
# {read_name} keyed on the LAST SEGMENT of a constant path, `name.delete_prefix("::").split("::").last`,
|
|
@@ -370,7 +538,7 @@ module Rigor
|
|
|
370
538
|
# objects a call over half a million calls. A name the scan could read differently from `split` (one
|
|
371
539
|
# that is not ASCII, or carries a run of colons or a trailing one) takes `split`'s answer.
|
|
372
540
|
def read_last_segment(kind, name)
|
|
373
|
-
return
|
|
541
|
+
return unless recording?
|
|
374
542
|
|
|
375
543
|
segment = last_segment(name)
|
|
376
544
|
read_name(kind, segment) if segment
|
|
@@ -394,15 +562,16 @@ module Rigor
|
|
|
394
562
|
# {read_name} for prebuilt, frozen `"kind:name"` keys: a caller that files the same keys on every read
|
|
395
563
|
# (a resolution chain's hook edges, #1641) builds them once and adds them here without a String apiece.
|
|
396
564
|
def read_keys(keys)
|
|
397
|
-
|
|
565
|
+
state = TypingState.current
|
|
566
|
+
accumulator = state[RECORDER]
|
|
398
567
|
return if accumulator.nil?
|
|
399
568
|
|
|
400
|
-
captures =
|
|
569
|
+
captures = state[CAPTURES]
|
|
401
570
|
open = captures && !captures.empty?
|
|
402
571
|
missing = accumulator.missing
|
|
403
572
|
keys.each do |entry|
|
|
404
|
-
tee_missing(captures, entry) if open
|
|
405
|
-
missing
|
|
573
|
+
tee_missing(state, captures, entry) if open
|
|
574
|
+
file_missing(state, accumulator, missing, entry)
|
|
406
575
|
end
|
|
407
576
|
end
|
|
408
577
|
|
|
@@ -412,7 +581,7 @@ module Rigor
|
|
|
412
581
|
# the name private passes `"*"` as the class; the key fires when any entry of the name moves. A wildcard
|
|
413
582
|
# slot (`[:singleton, :*]`) is `*` as the method name.
|
|
414
583
|
def read_visibility(class_name, method_name)
|
|
415
|
-
return
|
|
584
|
+
return unless recording?
|
|
416
585
|
|
|
417
586
|
read_name(:visibility, "#{class_name}##{method_name}")
|
|
418
587
|
end
|
|
@@ -423,18 +592,6 @@ module Rigor
|
|
|
423
592
|
def read_missing(kind, name)
|
|
424
593
|
read_name(kind, name)
|
|
425
594
|
end
|
|
426
|
-
|
|
427
|
-
# The positive-edge aggregation shared by {read_site} and {replay}. `sources` is the union of the other
|
|
428
|
-
# two tables' paths, and this is the only writer of all three, so a read the finer table already holds is
|
|
429
|
-
# already in `sources` and costs one Set probe instead of two.
|
|
430
|
-
def accumulate_read(accumulator, path, symbol)
|
|
431
|
-
if symbol
|
|
432
|
-
accumulator.sources << path if accumulator.symbol_sources[path].add?(symbol)
|
|
433
|
-
elsif accumulator.ancestry_sources.add?(path)
|
|
434
|
-
accumulator.sources << path
|
|
435
|
-
end
|
|
436
|
-
end
|
|
437
|
-
private_class_method :accumulate_read
|
|
438
595
|
end
|
|
439
596
|
end
|
|
440
597
|
end
|
|
@@ -25,9 +25,9 @@ module Rigor
|
|
|
25
25
|
@mutex = Mutex.new
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
# @return frozen snapshot of the recorded boundary-cross events. Each entry is a Data
|
|
29
|
-
#
|
|
30
|
-
#
|
|
28
|
+
# @return frozen snapshot of the recorded boundary-cross events. Each entry is a Data with `class_name`,
|
|
29
|
+
# `method_name`, `gem_name`, and `rbs_display` (the authoritative-side type's human-facing form, embedded
|
|
30
|
+
# into the diagnostic message).
|
|
31
31
|
def entries
|
|
32
32
|
@mutex.synchronize { @entries.dup.freeze }
|
|
33
33
|
end
|
|
@@ -50,6 +50,17 @@ module Rigor
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# The pool drain ({Rigor::Analysis::WorkerSession#drain_reporters}): the frozen entries.
|
|
54
|
+
def snapshot
|
|
55
|
+
entries
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Replays one worker's {#snapshot}; the record-time dedup collapses every worker's copy. Nil records
|
|
59
|
+
# nothing.
|
|
60
|
+
def absorb(snapshot)
|
|
61
|
+
Array(snapshot).each { |entry| record(**entry.to_h) }
|
|
62
|
+
end
|
|
63
|
+
|
|
53
64
|
private
|
|
54
65
|
|
|
55
66
|
def same_key?(existing, entry)
|
|
@@ -15,10 +15,9 @@ module Rigor
|
|
|
15
15
|
:class_to_gem, :budget_overrun_strategy, :gem_modes, :refinements, :bound_constants,
|
|
16
16
|
:bound_segments
|
|
17
17
|
|
|
18
|
-
# @param method_catalog —
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# attribution that slice 3's cache descriptor needs lives on `Resolved`, not here.
|
|
18
|
+
# @param method_catalog — the flat `(class_name, method_name) → CatalogEntry` table produced by
|
|
19
|
+
# {Walker.walk}, aggregated across every resolved gem in the run. The Index itself stays gem-agnostic —
|
|
20
|
+
# the per-gem attribution that slice 3's cache descriptor needs lives on `Resolved`, not here.
|
|
22
21
|
# @param budget_exceeded — gem names whose {Walker} run hit the per-gem catalog cap
|
|
23
22
|
# (slice 4). The Runner consumes this list to emit one `dynamic.dependency-source.budget-exceeded`
|
|
24
23
|
# warning per gem.
|
|
@@ -110,10 +109,9 @@ module Rigor
|
|
|
110
109
|
mode_for(class_name) == :full
|
|
111
110
|
end
|
|
112
111
|
|
|
113
|
-
# Looks up the
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
# build a `Type::Dynamic` directly from the lookup result.
|
|
112
|
+
# Looks up the `CatalogEntry` (kind, and the heuristic return type) for a `(class_name, method_name)` pair.
|
|
113
|
+
# Returns it when the walker observed a definition under one of the resolved gems' `roots:`, or `nil`
|
|
114
|
+
# otherwise.
|
|
117
115
|
def contribution_for(class_name:, method_name:)
|
|
118
116
|
@method_catalog[[class_name, method_name]]
|
|
119
117
|
end
|