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
|
@@ -27,10 +27,8 @@ module Rigor
|
|
|
27
27
|
#
|
|
28
28
|
# The invariant the verify harness (and the spec) assert: `#recheck`'s merged diagnostics are
|
|
29
29
|
# byte-identical to a full `--no-cache` re-analysis of the edited tree, in the same order (#1524; the gate
|
|
30
|
-
# itself compares sorted sets).
|
|
31
|
-
# persistence
|
|
32
|
-
# removes / moves a *file* is outside the analyzed set it maintains and falls to a fresh {#baseline} (the
|
|
33
|
-
# structural tier is a later slice).
|
|
30
|
+
# itself compares sorted sets). The `--verify-incremental` gate runs this engine; `#run_incremental` adds the
|
|
31
|
+
# snapshot persistence that `--incremental` uses.
|
|
34
32
|
# The class-length budget is relaxed: this is one cohesive orchestrator of the incremental state
|
|
35
33
|
# (per-file diagnostics cache, the file-level / symbol-level / negative dependency graphs, and the ADR-85
|
|
36
34
|
# seed bundles), clearer read together than split across micro-classes that would all share the same ivars.
|
|
@@ -101,8 +99,8 @@ module Rigor
|
|
|
101
99
|
# ADR-85 WD2 — per-file discovery seed bundles keyed by logical path. A cold baseline builds them; a
|
|
102
100
|
# warm recheck folds them (re-walking only changed files) and refreshes the set. Ride the snapshot.
|
|
103
101
|
@seed_bundles = {}
|
|
104
|
-
@cache = {} # analyzed path =>
|
|
105
|
-
@sources = {} # analyzed path =>
|
|
102
|
+
@cache = {} # analyzed path => its diagnostics
|
|
103
|
+
@sources = {} # analyzed path => the source paths it read from
|
|
106
104
|
@digests = {} # analyzed path => ADR-87 packed stat-digest entry at last analysis
|
|
107
105
|
# Issue #1583 — discovered-not-analysed path => the same packed entry, for a run over path arguments that
|
|
108
106
|
# #684 widened to the configured `paths:`. Empty on every run over the configured paths.
|
|
@@ -110,25 +108,29 @@ module Rigor
|
|
|
110
108
|
@analyzed = [] # the project files analyzed last round
|
|
111
109
|
@dependents = {} # inverted @sources (file-level)
|
|
112
110
|
# ADR-46 slice 4 — symbol-granularity tracking.
|
|
113
|
-
@symbol_sources = {} # consumer => { source_path =>
|
|
114
|
-
@ancestry_sources = {} # consumer =>
|
|
111
|
+
@symbol_sources = {} # consumer => { source_path => the "ClassName#method" symbols it read }
|
|
112
|
+
@ancestry_sources = {} # consumer => source paths (class-ancestry deps)
|
|
115
113
|
@symbol_fingerprints = {} # path => { "ClassName#method" => sha256_hex }
|
|
116
|
-
@symbol_dependents = {} # [source, symbol] =>
|
|
117
|
-
@ancestry_dependents = {} # source =>
|
|
114
|
+
@symbol_dependents = {} # [source, symbol] => consumers
|
|
115
|
+
@ancestry_dependents = {} # source => consumers (inverted ancestry_sources)
|
|
118
116
|
# ADR-46 slice 3 — negative (missing) dependencies: a consumer that
|
|
119
117
|
# looked up a name and resolved nothing must be re-checked when that
|
|
120
118
|
# name later appears (e.g. a `call.unresolved-toplevel` whose target
|
|
121
119
|
# is defined by a later edit).
|
|
122
|
-
@missing = {} # consumer =>
|
|
123
|
-
@negative_dependents = {} # "kind:name" =>
|
|
124
|
-
@class_decls = {} # path =>
|
|
120
|
+
@missing = {} # consumer => the "kind:name" keys it looked up and missed
|
|
121
|
+
@negative_dependents = {} # "kind:name" => consumers (inverted @missing)
|
|
122
|
+
@class_decls = {} # path => qualified class names declared in the file
|
|
125
123
|
# Issue #644 — the value-constant twin: path => { qualified constant name => publication descriptor }.
|
|
126
124
|
# Diffed on a recheck to find every constant whose PUBLISHED answer could have moved, which satisfies
|
|
127
125
|
# the `constant:` edges a reader records at every reference, resolved or not.
|
|
128
126
|
@constant_decls = {}
|
|
129
|
-
# ADR-89 WD2 — per-def observed-key return summaries: [path, symbol] => { keys:, returns:, effects
|
|
127
|
+
# ADR-89 WD2 — per-def observed-key return summaries: [path, symbol] => { keys:, returns:, effects:,
|
|
128
|
+
# guard_constants: }.
|
|
130
129
|
# Harvested from the ADR-84 return memo after each run; drives the behavioural-stability gate.
|
|
131
130
|
@return_summaries = {}
|
|
131
|
+
# Issue #3180 — what the ADR-89 WD2 gate's probe read for the pairs it passed, from the closure decision
|
|
132
|
+
# until {#absorb} folds it into the skipped readers; nil when no pair passed.
|
|
133
|
+
@gate_observations = nil
|
|
132
134
|
# ADR-67 WD6c lift — the `parameter_inference:` seed table the cached diagnostics were computed
|
|
133
135
|
# under (`{}` when the gate is off — the gate state is constant across a snapshot's lifetime because
|
|
134
136
|
# the configuration is part of the global fingerprint). A recheck recomputes the table fresh and
|
|
@@ -265,7 +267,7 @@ module Rigor
|
|
|
265
267
|
# digest (issue #1536).
|
|
266
268
|
@seed_bundles = source_rbs_gate.stamp(runner.seed_bundles, whole_project: true)
|
|
267
269
|
absorb_dependency_graph(runner)
|
|
268
|
-
@return_summaries = runner
|
|
270
|
+
@return_summaries = baseline_return_summaries(runner)
|
|
269
271
|
# ADR-67 WD6c lift — the seed table the runner's own pre-pass computed ({} when the gate is off).
|
|
270
272
|
# Reading it back, rather than computing it here, keeps the baseline single-collect.
|
|
271
273
|
@param_table = runner.param_inferred_types
|
|
@@ -393,6 +395,8 @@ module Rigor
|
|
|
393
395
|
# whole project — what a `sig/` edit already costs through the snapshot fingerprint.
|
|
394
396
|
def affected_closure(changed, added, removed, param_files = Set.new, param_pairs = Set.new)
|
|
395
397
|
@stale_plugin_readers = plugin_read_affected | disclosure_refreshers(removed)
|
|
398
|
+
# Issue #3180 — a recheck that passed the gate and then re-baselined absorbed nothing: start without it.
|
|
399
|
+
@gate_observations = nil
|
|
396
400
|
return whole_project_closure(changed, added) if source_rbs_gate.moved?(@seed_bundles, changed, added, removed)
|
|
397
401
|
|
|
398
402
|
(gated_closure(changed, added, removed, param_files, param_pairs) | @stale_plugin_readers).freeze
|
|
@@ -579,8 +583,10 @@ module Rigor
|
|
|
579
583
|
# surfaces are its return type and its content-mutation effects (no instance/class variable write, no
|
|
580
584
|
# `yield`, no implicit-self call whose transitive effect a caller could observe). For a candidate the
|
|
581
585
|
# session compares its content-mutation effects (pure AST) then re-evaluates its return at every
|
|
582
|
-
# persisted key (a runner probe)
|
|
583
|
-
#
|
|
586
|
+
# persisted key (a runner probe), with the published-constant spellings its body reads (issue #3186,
|
|
587
|
+
# {#guard_constants_unmoved?}); a pair passes only when all three are unchanged. Any missing summary (which is
|
|
588
|
+
# also how a def whose keys could not all be recorded is stored, issues #3116 and #3160), ineligible def,
|
|
589
|
+
# changed effect, changed return, or uncomputable key keeps the pair — the conservative
|
|
584
590
|
# direction the `--verify-incremental` and byte-identical recheck specs backstop.
|
|
585
591
|
def behaviourally_unstable_pairs(changed_pairs, unstable, scan_index)
|
|
586
592
|
return changed_pairs if @return_summaries.empty? || scan_index.nil?
|
|
@@ -633,9 +639,21 @@ module Rigor
|
|
|
633
639
|
runner = build_runner
|
|
634
640
|
files = @paths ? runner.analysis_file_set(@paths) : runner.analysis_file_set
|
|
635
641
|
rebind_buffer(files)
|
|
636
|
-
files
|
|
642
|
+
admit_new_buffer(files)
|
|
637
643
|
end
|
|
638
644
|
|
|
645
|
+
# Issue #2233 — a buffer for a file that is not on disk (a new file) is in no expansion, so the set above never
|
|
646
|
+
# names it: the recheck neither analyses it nor sees its declarations, and the run answers "re-analysed 0 file(s)"
|
|
647
|
+
# with no diagnostics for the buffer. Option A (single-file scope) analyses the logical path whether or not it
|
|
648
|
+
# exists; option B joins it to the set the same way, as an added file, so the closure analyses it like any new
|
|
649
|
+
# file. A buffer already matched by {#rebind_buffer} is in `files` and is returned as it is.
|
|
650
|
+
def admit_new_buffer(files)
|
|
651
|
+
return files if @buffer.nil? || files.include?(@buffer.logical_path)
|
|
652
|
+
|
|
653
|
+
files + [@buffer.logical_path]
|
|
654
|
+
end
|
|
655
|
+
private :admit_new_buffer
|
|
656
|
+
|
|
639
657
|
# Verification engine (the `--verify-incremental` gate): with NO source edit, re-analyze `subset` fresh
|
|
640
658
|
# and serve every other analyzed file from the baseline cache. Because nothing on disk changed, the
|
|
641
659
|
# merged result MUST equal a full analysis — so this exercises the subset-analysis and cache-merge
|
|
@@ -1226,10 +1244,79 @@ module Rigor
|
|
|
1226
1244
|
# with anything its closure demanded for itself, so this is the same union the next run replays.
|
|
1227
1245
|
@run_level_rows = runner.run_level_rows
|
|
1228
1246
|
@synthetic_surface_digest = synthetic_surface_digest_of(runner)
|
|
1229
|
-
|
|
1247
|
+
refresh_gated_state(runner, analyze_set)
|
|
1230
1248
|
refresh_effect_collections(runner, analyze_set)
|
|
1231
1249
|
end
|
|
1232
1250
|
|
|
1251
|
+
# The summaries, then the skipped readers' edges, in that order ({#refresh_return_summaries}), with what the
|
|
1252
|
+
# gate's probe observed for this recheck taken and cleared. The clear is a guard no spec pins:
|
|
1253
|
+
# {#affected_closure} resets the slot before every {#absorb}, so nothing reads it again. It only keeps a
|
|
1254
|
+
# consumed observation from outliving its recheck, should absorb gain another caller.
|
|
1255
|
+
def refresh_gated_state(runner, analyze_set)
|
|
1256
|
+
observations = @gate_observations
|
|
1257
|
+
@gate_observations = nil
|
|
1258
|
+
refresh_return_summaries(runner, analyze_set, observations)
|
|
1259
|
+
absorb_gate_reads(observations, analyze_set)
|
|
1260
|
+
end
|
|
1261
|
+
|
|
1262
|
+
# Issue #3180 — give every reader the ADR-89 WD2 gate skipped the edges its probe recorded for the pair it
|
|
1263
|
+
# passed ({#gate_observations}): the reader reaches those defs, classes and names through the edited def now,
|
|
1264
|
+
# and no analysis of the reader will record that until something else re-analyses it. The probe evaluated the
|
|
1265
|
+
# def at all of its stored keys and a reader observes it under some of them, so the reader may gain an edge it
|
|
1266
|
+
# does not need; that costs a re-analysis, never an answer. Runs after {#refresh_return_summaries}, which reads
|
|
1267
|
+
# the dependents as they stood before these edges. Independent of the summaries: a pooled recheck over
|
|
1268
|
+
# summaries a sequential run stored can pass the gate too, and its skipped readers need the edges as much.
|
|
1269
|
+
def absorb_gate_reads(observations, analyze_set)
|
|
1270
|
+
return if observations.nil?
|
|
1271
|
+
|
|
1272
|
+
analyzed = analyze_set.to_set
|
|
1273
|
+
skipped = observations.reads.keys.to_h do |pair|
|
|
1274
|
+
[pair, ((@symbol_dependents[pair] || Set.new) - analyzed).to_a]
|
|
1275
|
+
end
|
|
1276
|
+
return if skipped.each_value.all?(&:empty?)
|
|
1277
|
+
|
|
1278
|
+
credit_probe_plugin_rows(observations, skipped)
|
|
1279
|
+
skipped.each { |pair, readers| readers.each { |reader| add_recorded_reads(reader, observations.reads[pair]) } }
|
|
1280
|
+
invert_dependency_graph
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1283
|
+
# Issue #3187 — the plugin reads the probe made while evaluating a passed pair's def, credited to each reader of
|
|
1284
|
+
# the pair it skipped, as an analysis of that reader would have credited them: a later edit of what the plugin
|
|
1285
|
+
# read re-analyses the reader ({#plugin_read_affected}). Like the edges, a reader may gain a row it does not
|
|
1286
|
+
# need, which costs a re-analysis. Run-scoped registrations are not credited: a pair whose probe registered one
|
|
1287
|
+
# does not pass ({#readers_hold_disclosures?}).
|
|
1288
|
+
def credit_probe_plugin_rows(observations, skipped)
|
|
1289
|
+
skipped.each do |pair, readers|
|
|
1290
|
+
rows = observations.plugin_reads[pair]
|
|
1291
|
+
next if rows.nil?
|
|
1292
|
+
|
|
1293
|
+
readers.each { |reader| @plugin_reads[reader] = ((@plugin_reads[reader] || []) | rows).freeze }
|
|
1294
|
+
end
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
# `record`'s reads added to `reader`'s, less the reads of `reader`'s own file (a file records no edge to
|
|
1298
|
+
# itself). Builds new sets: a restored snapshot's are not this session's to mutate. The self-filter on the
|
|
1299
|
+
# ancestry edges is a guard no spec pins: it mirrors the two that are pinned, and no row was built in which
|
|
1300
|
+
# the edited def comes to read the ancestry of a class its skipped reader's file declares.
|
|
1301
|
+
def add_recorded_reads(reader, record)
|
|
1302
|
+
symbols = (@symbol_sources[reader] || {}).dup
|
|
1303
|
+
record.symbol_sources.each do |path, names|
|
|
1304
|
+
symbols[path] = (symbols[path] || Set.new) | names unless path == reader
|
|
1305
|
+
end
|
|
1306
|
+
@symbol_sources[reader] = symbols
|
|
1307
|
+
@ancestry_sources[reader] = (@ancestry_sources[reader] || Set.new) | (record.ancestry_sources - [reader])
|
|
1308
|
+
@sources[reader] = (@sources[reader] || Set.new) | (record.sources - [reader])
|
|
1309
|
+
@missing[reader] = (@missing[reader] || Set.new) | record.missing
|
|
1310
|
+
end
|
|
1311
|
+
|
|
1312
|
+
# The inverted indexes of the four per-consumer edge tables.
|
|
1313
|
+
def invert_dependency_graph
|
|
1314
|
+
@dependents = Incremental.invert(@sources)
|
|
1315
|
+
@symbol_dependents = Incremental.invert_symbols(@symbol_sources)
|
|
1316
|
+
@ancestry_dependents = Incremental.invert(@ancestry_sources)
|
|
1317
|
+
@negative_dependents = Incremental.invert(@missing)
|
|
1318
|
+
end
|
|
1319
|
+
|
|
1233
1320
|
# ADR-103 WD13 / #382 — the effects analogue of {#refresh_return_summaries}, and the same soundness
|
|
1234
1321
|
# order: drop every re-analyzed file's collection first, then fold this run's harvest in. A file the
|
|
1235
1322
|
# recheck did NOT analyze keeps the collection the snapshot carried, which is exactly the reuse the
|
|
@@ -1245,21 +1332,97 @@ module Rigor
|
|
|
1245
1332
|
@effect_plugin_digest = effect_plugin_digest_of(runner)
|
|
1246
1333
|
end
|
|
1247
1334
|
|
|
1248
|
-
# ADR-89 WD2 —
|
|
1249
|
-
# the
|
|
1250
|
-
#
|
|
1251
|
-
#
|
|
1252
|
-
#
|
|
1253
|
-
#
|
|
1254
|
-
#
|
|
1255
|
-
#
|
|
1256
|
-
#
|
|
1257
|
-
#
|
|
1258
|
-
#
|
|
1259
|
-
|
|
1335
|
+
# ADR-89 WD2 / issues #3160, #3180 — bring the behavioural summaries up to date with THIS run. The invariant
|
|
1336
|
+
# the gate reads them under: a pair's summary holds every key under which a CURRENT symbol dependent of the
|
|
1337
|
+
# def observes it, directly or through other defs, each with the def's current return; and every file that
|
|
1338
|
+
# observes the def is one of its symbol dependents. A summary that cannot be shown to is deleted, and a pair
|
|
1339
|
+
# with no summary keeps its dependents, so every doubt below resolves to "no summary".
|
|
1340
|
+
#
|
|
1341
|
+
# A run's harvest ({Runner#return_summaries}) is what the files it analysed observed. The keys the gate's
|
|
1342
|
+
# probe observed for the pairs it passed (`observations`, {#gate_observations}) count as this run's too: they
|
|
1343
|
+
# are what the readers the gate skipped observe through the edited def. So per pair:
|
|
1344
|
+
#
|
|
1345
|
+
# - the run cannot list the keys it observed (a pool run, a def past the per-def or total cap, a call the
|
|
1346
|
+
# memo refused to store): delete;
|
|
1347
|
+
# - every dependent was analysed: the harvest is the whole key set, and replaces the summary (deleting it
|
|
1348
|
+
# when no analysed file observed the def, which is where a def with no dependent left ends up);
|
|
1349
|
+
# - otherwise the summary keeps the keys of the dependents that were not analysed, and gains this run's
|
|
1350
|
+
# ({#merged_return_summary}), which are none when no dependent was analysed. The kept returns are current:
|
|
1351
|
+
# a dependent stays out of the closure of its callee's edit only when the gate proved the returns unmoved
|
|
1352
|
+
# at every kept key, or when the def's own text did not change. With no summary to keep, the other
|
|
1353
|
+
# dependents' keys are unknown.
|
|
1354
|
+
#
|
|
1355
|
+
# Fewer keys are never the conservative direction: each key protects the readers that observe the def under
|
|
1356
|
+
# it, so a summary missing one skips a reader whose answer the edit moved.
|
|
1357
|
+
#
|
|
1358
|
+
# "Every dependent" is read off the edges as they stood BEFORE {#absorb_gate_reads} adds the probe's. A
|
|
1359
|
+
# skipped reader that gains its first edge to a def there observes it only through the pair the gate passed,
|
|
1360
|
+
# so the probe's keys are all of its keys; one that already held an edge was not analysed, and the pair keeps
|
|
1361
|
+
# its stored keys.
|
|
1362
|
+
def refresh_return_summaries(runner, analyze_set, observations = nil)
|
|
1363
|
+
harvest = harvest_with_gate_summaries(runner.return_summaries, observations&.summaries)
|
|
1260
1364
|
analyzed = analyze_set.to_set
|
|
1261
|
-
@return_summaries.
|
|
1262
|
-
|
|
1365
|
+
(@return_summaries.keys | (harvest || {}).keys).each do |pair|
|
|
1366
|
+
dependents = @symbol_dependents[pair] || Set.new
|
|
1367
|
+
summary = refreshed_return_summary(pair, harvest, dependents.subset?(analyzed))
|
|
1368
|
+
summary.nil? ? @return_summaries.delete(pair) : @return_summaries[pair] = summary
|
|
1369
|
+
end
|
|
1370
|
+
@return_summaries = capped_return_summaries(@return_summaries)
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
# Issue #3180 — `harvest` (this run's) with the keys the gate's probe observed (`probed`, see
|
|
1374
|
+
# {#gate_observations}) counted as observed by this run: they are what the readers the gate skipped observe
|
|
1375
|
+
# through the def it passed, each with the callee's current return. A def either side cannot list the keys
|
|
1376
|
+
# of has none, and so has one whose keys together pass the per-def cap. A run with no harvest stays without.
|
|
1377
|
+
def harvest_with_gate_summaries(harvest, probed)
|
|
1378
|
+
return harvest if harvest.nil? || probed.nil? || probed.empty?
|
|
1379
|
+
|
|
1380
|
+
probed.each_with_object(harvest.dup) do |(pair, summary), merged|
|
|
1381
|
+
mine = merged.fetch(pair, summary)
|
|
1382
|
+
merged[pair] = mine.nil? || summary.nil? ? nil : merged_return_summary(mine, summary)
|
|
1383
|
+
end
|
|
1384
|
+
end
|
|
1385
|
+
|
|
1386
|
+
# ADR-89 WD2 — the full-run behavioural surface: every file was analysed, so the harvest is every reader's.
|
|
1387
|
+
# A def the run could not list the keys of (nil in the harvest) and a pool run (no harvest) record nothing.
|
|
1388
|
+
def baseline_return_summaries(runner)
|
|
1389
|
+
capped_return_summaries((runner.return_summaries || {}).compact)
|
|
1390
|
+
end
|
|
1391
|
+
|
|
1392
|
+
# `pair`'s summary after a run whose harvest is `harvest` (nil for a run that has none), or nil for none.
|
|
1393
|
+
# `complete` says every dependent of the def was analysed by that run.
|
|
1394
|
+
def refreshed_return_summary(pair, harvest, complete)
|
|
1395
|
+
return nil if harvest.nil?
|
|
1396
|
+
|
|
1397
|
+
fresh = harvest[pair]
|
|
1398
|
+
return nil if fresh.nil? && harvest.key?(pair)
|
|
1399
|
+
return fresh if complete
|
|
1400
|
+
|
|
1401
|
+
merged_return_summary(@return_summaries[pair], fresh)
|
|
1402
|
+
end
|
|
1403
|
+
|
|
1404
|
+
# Issue #3160 — `kept` (the summary of a def some of whose dependents this run did not analyse) with the
|
|
1405
|
+
# keys `fresh` (this run's harvest for the def, or nil) adds. A kept key stays unless `fresh` holds the same
|
|
1406
|
+
# key with the same return; one whose return differs stays beside it, which no re-evaluation can satisfy, so
|
|
1407
|
+
# the gate keeps the def's dependents. nil when there is nothing to keep or the keys pass the per-def cap.
|
|
1408
|
+
def merged_return_summary(kept, fresh)
|
|
1409
|
+
return nil if kept.nil?
|
|
1410
|
+
return kept if fresh.nil?
|
|
1411
|
+
|
|
1412
|
+
rows = fresh[:keys].zip(fresh[:returns])
|
|
1413
|
+
rows += kept[:keys].zip(kept[:returns]).reject { |row| rows.include?(row) }
|
|
1414
|
+
return nil if rows.size > Runner::RETURN_SUMMARY_KEYS_PER_DEF
|
|
1415
|
+
|
|
1416
|
+
{ keys: rows.map(&:first), returns: rows.map(&:last), effects: fresh[:effects],
|
|
1417
|
+
guard_constants: fresh[:guard_constants] }
|
|
1418
|
+
end
|
|
1419
|
+
|
|
1420
|
+
# The first {Runner::RETURN_SUMMARY_TOTAL_CAP} of `summaries`, so the snapshot stays small however many runs
|
|
1421
|
+
# fed it. A def past the cap has no summary, which keeps its dependents.
|
|
1422
|
+
def capped_return_summaries(summaries)
|
|
1423
|
+
return summaries if summaries.size <= Runner::RETURN_SUMMARY_TOTAL_CAP
|
|
1424
|
+
|
|
1425
|
+
summaries.first(Runner::RETURN_SUMMARY_TOTAL_CAP).to_h
|
|
1263
1426
|
end
|
|
1264
1427
|
|
|
1265
1428
|
# Evict a removed file from every per-file map so its stale diagnostics are never served and it drops
|
|
@@ -1292,10 +1455,7 @@ module Rigor
|
|
|
1292
1455
|
@ancestry_sources[path] = record.ancestry_sources.dup
|
|
1293
1456
|
@missing[path] = record.missing.dup
|
|
1294
1457
|
end
|
|
1295
|
-
|
|
1296
|
-
@symbol_dependents = Incremental.invert_symbols(@symbol_sources)
|
|
1297
|
-
@ancestry_dependents = Incremental.invert(@ancestry_sources)
|
|
1298
|
-
@negative_dependents = Incremental.invert(@missing)
|
|
1458
|
+
invert_dependency_graph
|
|
1299
1459
|
@symbol_fingerprints.merge!(runner.symbol_fingerprints)
|
|
1300
1460
|
# Wholesale replace (the subset runner's pre-pass is complete): a file that lost its last class must
|
|
1301
1461
|
# drop out of the map so a later re-add registers as an appearance.
|
|
@@ -1384,6 +1544,11 @@ module Rigor
|
|
|
1384
1544
|
# (`ResolutionChain#record_hooks`), so a new file that taints `Comparable` with `include(*MODS)` re-checks the
|
|
1385
1545
|
# readers of a chain through it. It is not `mark:`, which a plain reopening of a module moves (the bucket's
|
|
1386
1546
|
# module mark) and which would re-check every reader of `Kernel` on any `module Kernel` reopening.
|
|
1547
|
+
#
|
|
1548
|
+
# Issue #3005 — a moved {Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK} fires the owner's `mixin:` keys too: a new
|
|
1549
|
+
# file whose body-level `send(:then)` marks an external `Comparable` makes a reader of a chain through it decline,
|
|
1550
|
+
# and that reader filed `mixin:Comparable`, not `mark:Comparable`. The mark moves only with such a call, not on a
|
|
1551
|
+
# plain reopening.
|
|
1387
1552
|
def mark_affected(paths, summary)
|
|
1388
1553
|
def_index = summary && summary[:def_index]
|
|
1389
1554
|
before = paths.map { |path| @seed_bundles.dig(path, :parameter_envelopes) }
|
|
@@ -1391,16 +1556,22 @@ module Rigor
|
|
|
1391
1556
|
tainted = Incremental.changed_unpositioned_owners(
|
|
1392
1557
|
paths.map { |path| @seed_bundles.dig(path, :unpositioned_mixins) },
|
|
1393
1558
|
[def_index && def_index[:unpositioned_mixins]]
|
|
1394
|
-
)
|
|
1559
|
+
) | dynamic_owners(moved)
|
|
1395
1560
|
return Set.new if moved.empty? && tainted.empty?
|
|
1396
1561
|
|
|
1397
1562
|
keys = moved.map { |(class_name, _mark)| "mark:#{class_name.split('::').last}" }.uniq
|
|
1398
1563
|
keys.concat(mixin_keys(tainted))
|
|
1399
1564
|
extended = Scope::DiscoveryIndex::ENVELOPE_OBJECT_EXTENDED_MARK
|
|
1400
|
-
|
|
1565
|
+
meta = Scope::DiscoveryIndex::ENVELOPE_META_SUPERCLASS_MARK
|
|
1566
|
+
keys << "mark:*" if moved.any? { |(_class_name, mark)| mark == extended || mark == meta }
|
|
1401
1567
|
Incremental.negative_closure(keys, @negative_dependents)
|
|
1402
1568
|
end
|
|
1403
1569
|
|
|
1570
|
+
def dynamic_owners(moved)
|
|
1571
|
+
dynamic = Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK
|
|
1572
|
+
moved.filter_map { |(class_name, mark)| class_name if mark == dynamic }
|
|
1573
|
+
end
|
|
1574
|
+
|
|
1404
1575
|
# Issue #1661 — the `mixin:` keys an owner of a moved unpositioned edge fires. The table keys a receiver-form
|
|
1405
1576
|
# owner by the spelling written (`class File; Constants.include(*MODS); end` is `"Constants"`), not the constant
|
|
1406
1577
|
# Ruby resolves it to, while the chain files the qualified candidate (`mixin:File::Constants`). The table does
|
|
@@ -1505,7 +1676,7 @@ module Rigor
|
|
|
1505
1676
|
end
|
|
1506
1677
|
|
|
1507
1678
|
# The qualified class/module names declared in the pre-parsed `index` (shared with
|
|
1508
|
-
# {#symbol_fingerprints_from_index}, so the changed set is parsed once)
|
|
1679
|
+
# {#symbol_fingerprints_from_index}, so the changed set is parsed once), keyed by path.
|
|
1509
1680
|
def class_declarations_from_index(index)
|
|
1510
1681
|
return {} if index.nil?
|
|
1511
1682
|
|
|
@@ -1545,7 +1716,9 @@ module Rigor
|
|
|
1545
1716
|
|
|
1546
1717
|
# ADR-89 WD2 — true when the ONLY cross-file body surfaces of `node` a symbol dependent can consume are
|
|
1547
1718
|
# its return type and its content-mutation effects — so comparing exactly those two is a COMPLETE
|
|
1548
|
-
# behavioural check.
|
|
1719
|
+
# behavioural check. (Issue #3186: the truthiness rule's one-hop body scan reads a third, the published
|
|
1720
|
+
# constants the body names, which the probe compares too; no node kind makes a def ineligible for it.)
|
|
1721
|
+
# A def qualifies when its body writes no instance / class variable (an ivar
|
|
1549
1722
|
# definite-assignment a same-class caller consumes), does not `yield` (a block-parameter surface a
|
|
1550
1723
|
# caller passing a block consumes), and makes no implicit-self call (which could carry a transitive
|
|
1551
1724
|
# shared-state write). Deliberately conservative + purely syntactic: a def with a self-call or a
|
|
@@ -1597,29 +1770,104 @@ module Rigor
|
|
|
1597
1770
|
end
|
|
1598
1771
|
|
|
1599
1772
|
def content_effects_for(node)
|
|
1600
|
-
|
|
1601
|
-
.content_mutated_parameter_positions(node)
|
|
1773
|
+
effects_evaluator.content_mutated_parameter_positions(node)
|
|
1602
1774
|
rescue StandardError
|
|
1603
1775
|
nil
|
|
1604
1776
|
end
|
|
1605
1777
|
|
|
1778
|
+
# The evaluator {#content_effects_for} asks, kept for as long as the calling thread's {TypingState} is the one it
|
|
1779
|
+
# was built under. An evaluator caches that state, and the owner evaluators it builds cache theirs, so one kept
|
|
1780
|
+
# across the language server's save rounds, each on a thread of its own, would keep writing its run-scoped memos
|
|
1781
|
+
# into a finished round's state, which that round's release has already emptied (ADR-129).
|
|
1782
|
+
def effects_evaluator
|
|
1783
|
+
state = TypingState.current
|
|
1784
|
+
unless @effects_state.equal?(state)
|
|
1785
|
+
@effects_state = state
|
|
1786
|
+
@effects_evaluator = Inference::StatementEvaluator.new(scope: Scope.empty)
|
|
1787
|
+
end
|
|
1788
|
+
@effects_evaluator
|
|
1789
|
+
end
|
|
1790
|
+
|
|
1606
1791
|
# ADR-89 WD2 — the subset of `pairs` whose (edited) def re-evaluates to the SAME return descriptor at
|
|
1607
1792
|
# every persisted key. Runs one runner probe (`evaluate_return_types` — builds the discovery + env once,
|
|
1608
1793
|
# no file analysis) over all candidate defs, then keeps a pair only when every key's re-evaluated
|
|
1609
1794
|
# descriptor is present (not a memo refusal) and equal to the persisted one. A probe failure yields an
|
|
1610
1795
|
# empty set (keep everything).
|
|
1611
1796
|
def return_stable_pairs(pairs, scan_index)
|
|
1612
|
-
specs = pairs.
|
|
1797
|
+
specs = pairs.reject { |pair| readers_hold_disclosures?(pair) }.filter_map do |pair|
|
|
1798
|
+
return_spec_for(pair, scan_index)
|
|
1799
|
+
end
|
|
1613
1800
|
return Set.new if specs.empty?
|
|
1614
1801
|
|
|
1615
|
-
results = probe_return_types(specs)
|
|
1616
|
-
specs.each_with_object(
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
next
|
|
1620
|
-
|
|
1802
|
+
runner, results = probe_return_types(specs)
|
|
1803
|
+
passed = specs.each_with_object({}) do |spec, pairs|
|
|
1804
|
+
triple = [spec[:class_name], spec[:method_name], spec[:singleton]]
|
|
1805
|
+
next unless returns_unmoved?(spec[:pair], results[triple])
|
|
1806
|
+
next unless guard_constants_unmoved?(spec[:pair], runner.probe_guard_constants[triple])
|
|
1807
|
+
# Issue #3187 — a run-scoped row the probe registered keeps the readers ({#readers_hold_disclosures?}).
|
|
1808
|
+
next if runner.probe_disclosures.key?(triple)
|
|
1621
1809
|
|
|
1622
|
-
|
|
1810
|
+
pairs[spec[:pair]] = triple
|
|
1811
|
+
end
|
|
1812
|
+
@gate_observations = gate_observations(runner, passed) unless passed.empty?
|
|
1813
|
+
passed.keys.to_set
|
|
1814
|
+
end
|
|
1815
|
+
|
|
1816
|
+
# Issue #3187 — whether a symbol dependent of `pair` holds a run-scoped plugin row ({RunDisclosureLedger}), which
|
|
1817
|
+
# keeps the pair's readers, as a probe that registers one does. A skipped reader replays the rows its last
|
|
1818
|
+
# analysis registered, and which of them came through the edited def, under which key, is not recorded; nor
|
|
1819
|
+
# can rows the probe registers be credited to a reader without that key, or ever be retracted once its def
|
|
1820
|
+
# stops registering them. So a reader holding any row is re-analysed, which settles its rows afresh.
|
|
1821
|
+
def readers_hold_disclosures?(pair)
|
|
1822
|
+
(@symbol_dependents[pair] || Set.new).any? { |reader| @plugin_disclosures.files.key?(reader) }
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1825
|
+
# Issue #3186 — whether the probe's `current` constant spellings ({Runner#guard_constants}) are `pair`'s stored
|
|
1826
|
+
# ones. A reader's `flow.always-truthy-condition` withholds when a def it calls reads a constant another file
|
|
1827
|
+
# published, whatever the def returns, so a return-preserving edit that adds or drops such a read moves the
|
|
1828
|
+
# reader's diagnostics. nil on either side (a list the runner could not make, a summary stored before the list
|
|
1829
|
+
# existed) reads as moved.
|
|
1830
|
+
def guard_constants_unmoved?(pair, current)
|
|
1831
|
+
stored = @return_summaries[pair][:guard_constants]
|
|
1832
|
+
!current.nil? && !stored.nil? && current == stored
|
|
1833
|
+
end
|
|
1834
|
+
|
|
1835
|
+
# Whether the probe's `descriptors` are `pair`'s stored returns, key for key, none of them a memo refusal.
|
|
1836
|
+
def returns_unmoved?(pair, descriptors)
|
|
1837
|
+
expected = @return_summaries[pair][:returns]
|
|
1838
|
+
return false if descriptors.nil? || descriptors.length != expected.length
|
|
1839
|
+
|
|
1840
|
+
descriptors.each_with_index.all? { |descriptor, index| !descriptor.nil? && descriptor == expected[index] }
|
|
1841
|
+
end
|
|
1842
|
+
|
|
1843
|
+
# Issue #3180 — what the gate's probe observed for the pairs it passed. A reader the gate skips is not
|
|
1844
|
+
# re-analysed, yet it now reaches whatever the edited def reaches: a callee the def did not call before, or
|
|
1845
|
+
# one it calls under another key. The probe evaluated the def at every key its readers observe it under, so
|
|
1846
|
+
# its reads are a superset of theirs through this def, and its memo holds every key those evaluations put to
|
|
1847
|
+
# another def. `reads` is each passing pair's {DependencyRecorder::Record}; `summaries` is the probe's own
|
|
1848
|
+
# harvest ({Runner#return_summaries}: nil for a def it cannot list the keys of, nil as a whole in pool mode),
|
|
1849
|
+
# cut down to the defs a passing pair read. A failing pair's observations are left out: its readers are all
|
|
1850
|
+
# re-analysed, and the run's harvest speaks for them. Issue #3187 — `plugin_reads` is each passing pair's plugin
|
|
1851
|
+
# boundary rows ({Runner#probe_plugin_reads}); a pair whose evaluation read none has no key.
|
|
1852
|
+
GateObservations = Data.define(:reads, :summaries, :plugin_reads)
|
|
1853
|
+
private_constant :GateObservations
|
|
1854
|
+
|
|
1855
|
+
# `passed` maps each passing pair to its spec triple. The probe recorded reads for every def it evaluated, and
|
|
1856
|
+
# it evaluated each of these: a summary holds at least one key, so a def the probe could not find answered nil
|
|
1857
|
+
# and did not pass.
|
|
1858
|
+
def gate_observations(runner, passed)
|
|
1859
|
+
reads = passed.transform_values { |triple| runner.probe_reads[triple] }
|
|
1860
|
+
observed = reads.each_value.with_object(Set.new) do |record, pairs|
|
|
1861
|
+
record.symbol_sources.each { |path, symbols| symbols.each { |symbol| pairs << [path, symbol] } }
|
|
1862
|
+
end
|
|
1863
|
+
GateObservations.new(reads: reads, summaries: runner.return_summaries&.slice(*observed),
|
|
1864
|
+
plugin_reads: probe_rows(passed, runner.probe_plugin_reads))
|
|
1865
|
+
end
|
|
1866
|
+
|
|
1867
|
+
# `table` (spec triple => rows) re-keyed by each passing pair, leaving out a pair with no rows.
|
|
1868
|
+
def probe_rows(passed, table)
|
|
1869
|
+
passed.each_with_object({}) do |(pair, triple), rows|
|
|
1870
|
+
rows[pair] = table[triple] if table.key?(triple)
|
|
1623
1871
|
end
|
|
1624
1872
|
end
|
|
1625
1873
|
|
|
@@ -1634,10 +1882,15 @@ module Rigor
|
|
|
1634
1882
|
singleton: singleton, keys: @return_summaries[pair][:keys] }
|
|
1635
1883
|
end
|
|
1636
1884
|
|
|
1885
|
+
# The probe's runner and its `spec triple => descriptors` answer ({} when the probe failed).
|
|
1637
1886
|
def probe_return_types(specs)
|
|
1638
|
-
build_runner
|
|
1887
|
+
runner = build_runner
|
|
1888
|
+
[runner, runner.evaluate_return_types(@paths, specs)]
|
|
1889
|
+
rescue Inference::MemoAudit::Mismatch
|
|
1890
|
+
# ADR-129 WD5 (#2785) — the audit's mismatch (raised by the run's release) fails the session, not the probe.
|
|
1891
|
+
raise
|
|
1639
1892
|
rescue StandardError
|
|
1640
|
-
{}
|
|
1893
|
+
[runner, {}]
|
|
1641
1894
|
end
|
|
1642
1895
|
|
|
1643
1896
|
TOP_LEVEL_KEY = Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../../plugin"
|
|
4
4
|
require_relative "../../plugin/loader"
|
|
5
5
|
require_relative "../../plugin/services"
|
|
6
|
+
require_relative "../../plugin/trust_policy"
|
|
6
7
|
require_relative "../../reflection"
|
|
7
8
|
require_relative "../../type/combinator"
|
|
8
9
|
|
|
@@ -107,14 +108,16 @@ module Rigor
|
|
|
107
108
|
end
|
|
108
109
|
|
|
109
110
|
# Mirrors `Analysis::WorkerSession`'s services: the shared fact store plus whatever cache store the
|
|
110
|
-
# caller holds
|
|
111
|
-
#
|
|
111
|
+
# caller holds, and the run's trust policy — `rigor unused`'s plugin roots read the same scope
|
|
112
|
+
# `rigor check` does (#2586). Holding the `Services` is what gives access to the fact store the
|
|
113
|
+
# loaded plugins share — the loader hands the same instance to every plugin.
|
|
112
114
|
def build_services(configuration, cache_store)
|
|
113
115
|
Plugin::Services.new(
|
|
114
116
|
reflection: Reflection,
|
|
115
117
|
type: Type::Combinator,
|
|
116
118
|
configuration: configuration,
|
|
117
|
-
cache_store: cache_store
|
|
119
|
+
cache_store: cache_store,
|
|
120
|
+
trust_policy: Plugin::TrustPolicy.for_configuration(configuration)
|
|
118
121
|
)
|
|
119
122
|
end
|
|
120
123
|
|
|
@@ -25,7 +25,7 @@ module Rigor
|
|
|
25
25
|
# profile, taken from `Configuration::SeverityProfile::PROFILES`.
|
|
26
26
|
# - `evidence_tier` — `:high` / `:medium` / `:low` (or `nil` for informational helpers), Rigor's own
|
|
27
27
|
# confidence that a firing is a true positive, derived from the rule's firing gates. `:high` rules
|
|
28
|
-
# fire only on a concrete, statically-
|
|
28
|
+
# fire only on a concrete, statically-known type with no metaprogramming escape (Rigor's
|
|
29
29
|
# false-positive discipline has already filtered the uncertain cases); `:medium` rules rest on flow /
|
|
30
30
|
# inference proofs that inherit a documented FP envelope (loop / mutation / RBS-strictness modelling
|
|
31
31
|
# gaps); `:low` rules are resolution- or coverage-gap signals where a firing often reflects missing
|
|
@@ -101,7 +101,9 @@ module Rigor
|
|
|
101
101
|
"of the class are open, and an inherited Object / Kernel signature no longer decides " \
|
|
102
102
|
"`call.wrong-arity` or `call.argument-type-mismatch` on it.",
|
|
103
103
|
"The program may answer the call another way (#1722): a `method_missing` on the receiver's class or an " \
|
|
104
|
-
"ancestor
|
|
104
|
+
"ancestor, in source or in RBS (#2792; an RBS `method_missing` on `BasicObject`, `Object` or " \
|
|
105
|
+
"`Kernel` does not count); " \
|
|
106
|
+
"a module mixed into either that defines the method (`Integer.include(Ext)` counts wherever " \
|
|
105
107
|
"it is written); singleton methods given to the receiver local (`s.extend(M)`, " \
|
|
106
108
|
"`define_singleton_method`, `class << s`); or, for a receiver typed by its class's name rather than a " \
|
|
107
109
|
"literal, a project subclass that defines the method."
|
|
@@ -1087,6 +1089,40 @@ module Rigor
|
|
|
1087
1089
|
since: "0.4.0"
|
|
1088
1090
|
),
|
|
1089
1091
|
|
|
1092
|
+
CheckRules::RULE_PLUGIN_API_SURFACE => Entry.new(
|
|
1093
|
+
id: CheckRules::RULE_PLUGIN_API_SURFACE,
|
|
1094
|
+
summary: "Plugin code calls a member a `Rigor::Plugin` interface does not declare.",
|
|
1095
|
+
fires_when: [
|
|
1096
|
+
"The call's receiver is typed by a `Rigor::Plugin` interface (`_Scope`, `_Environment`, " \
|
|
1097
|
+
"`_Services`, `_FactStore`, `_Configuration`), possibly with `nil`: a hook's `scope`, " \
|
|
1098
|
+
"`scope.environment`, `services.fact_store`, and so on.",
|
|
1099
|
+
"The interface does not declare the called method, and neither does `Object` (with `Kernel` and " \
|
|
1100
|
+
"`BasicObject`).",
|
|
1101
|
+
"The call sits inside a class whose superclass chain reaches `Rigor::Plugin::Base`.",
|
|
1102
|
+
"The `plugin-api-surface` bleeding-edge feature is enabled, or a `severity_overrides:` key names " \
|
|
1103
|
+
"`plugin.api-surface` (the rule resolves `:off` otherwise; a dot-free `plugin` key does not reach it)."
|
|
1104
|
+
],
|
|
1105
|
+
does_not_fire_when: [
|
|
1106
|
+
"The interface declares the method (`scope.type_of(node)`), or every object answers it " \
|
|
1107
|
+
"(`scope.nil?`, `scope.respond_to?(...)`). A call that a `respond_to?` guard protects still fires.",
|
|
1108
|
+
"The value lost its interface type: a join with an untyped value, a helper parameter with no " \
|
|
1109
|
+
"signature, or the result of a `&.` call on a value that may be nil. It reads as `untyped` there.",
|
|
1110
|
+
"The call is outside a `Rigor::Plugin::Base` subclass, or inside `Rigor::Plugin::Base` itself.",
|
|
1111
|
+
"The `plugin-api-surface` bleeding-edge feature is not enabled (the default)."
|
|
1112
|
+
],
|
|
1113
|
+
suppression: "`# rigor:disable plugin.api-surface` on the call line, or " \
|
|
1114
|
+
"`disable: [\"plugin.api-surface\"]` in `.rigor.yml`. The fix is to call a member the " \
|
|
1115
|
+
"interface declares or a public namespace (`Rigor::Reflection`, ...).",
|
|
1116
|
+
severity_authored: :warning,
|
|
1117
|
+
severity_by_profile: { lenient: :off, balanced: :off, strict: :off },
|
|
1118
|
+
# The receiver's interface type comes from Rigor's own signatures and the member list is the
|
|
1119
|
+
# interface's declaration, so a firing is a call outside the declared surface. It ships `:off`
|
|
1120
|
+
# behind `bleeding_edge:` because it is a new discipline (ADR-50 Decision 3), not for lack of
|
|
1121
|
+
# confidence.
|
|
1122
|
+
evidence_tier: :high,
|
|
1123
|
+
since: "0.4.8"
|
|
1124
|
+
),
|
|
1125
|
+
|
|
1090
1126
|
CheckRules::RULE_SUPPRESSION_UNKNOWN_MARKER => Entry.new(
|
|
1091
1127
|
id: CheckRules::RULE_SUPPRESSION_UNKNOWN_MARKER,
|
|
1092
1128
|
summary: "A comment uses a suppression marker Rigor does not recognise " \
|
|
@@ -1120,7 +1156,7 @@ module Rigor
|
|
|
1120
1156
|
|
|
1121
1157
|
module_function
|
|
1122
1158
|
|
|
1123
|
-
# Looks up a rule by canonical id, legacy alias, or family wildcard. Returns
|
|
1159
|
+
# Looks up a rule by canonical id, legacy alias, or family wildcard. Returns the matching entries:
|
|
1124
1160
|
#
|
|
1125
1161
|
# - canonical id → 1-element array,
|
|
1126
1162
|
# - legacy alias → 1-element array (resolved to canonical),
|