rigortype 0.4.5 → 0.4.7
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 +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- 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 +5 -2
- data/docs/handbook/11-sig-gen.md +61 -5
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +192 -27
- data/docs/manual/03-configuration.md +38 -4
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +47 -3
- data/docs/manual/12-caching.md +9 -1
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +49 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +2 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- 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 +53 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +217 -52
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +15 -4
- 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 +154 -0
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +37 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +202 -43
- data/lib/rigor/analysis/path_expansion.rb +41 -0
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +7 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
- data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
- 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/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +233 -120
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- 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 +38 -33
- data/lib/rigor/bleeding_edge.rb +3 -6
- 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/incremental_snapshot.rb +60 -18
- 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 +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -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 +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +188 -93
- 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 +27 -21
- 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 +18 -17
- 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 +17 -13
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +14 -8
- 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 +60 -4
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +22 -18
- data/lib/rigor/cli/plugins_renderer.rb +34 -8
- 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 +23 -16
- data/lib/rigor/cli/sig_gen_command.rb +130 -34
- 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 +1 -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 +18 -12
- data/lib/rigor/cli/type_of_project_scope.rb +24 -0
- 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 +209 -54
- data/lib/rigor/config_audit.rb +17 -6
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +86 -34
- 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 +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -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/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_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +592 -70
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +136 -32
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +44 -11
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -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 +126 -0
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +170 -0
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +204 -12
- data/lib/rigor/inference/definer_resolution.rb +21 -5
- data/lib/rigor/inference/definers_index.rb +106 -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 +17 -3
- data/lib/rigor/inference/expression_typer.rb +173 -57
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +110 -1
- 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/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +246 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +57 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- 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 +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- 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 +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- 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/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 +109 -29
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +23 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +162 -29
- data/lib/rigor/inference/narrowing.rb +113 -38
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/optimistic_origin.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/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_seed_tables.rb +2 -1
- data/lib/rigor/inference/project_yielder.rb +122 -0
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -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 +9 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
- 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 +135 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +614 -146
- data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +3036 -278
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +9 -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 +28 -9
- 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 +98 -4
- 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 +12 -6
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +82 -3
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +26 -11
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- 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 +38 -16
- data/lib/rigor/plugin/registry.rb +41 -3
- 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 +2 -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 +96 -2
- 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 +5 -3
- data/lib/rigor/rooted_glob.rb +25 -2
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +379 -36
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +668 -84
- 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 +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +7 -6
- 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/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +21 -12
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- 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 +217 -37
- 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 +13 -16
- data/lib/rigor/type/constant.rb +48 -7
- data/lib/rigor/type/hash_shape.rb +26 -40
- data/lib/rigor/type/struct_instance.rb +3 -2
- data/lib/rigor/type/union.rb +1 -1
- 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/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 +89 -70
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -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 +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
- 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 +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- 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 +7 -12
- 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 +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -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 +7 -6
- 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 +4 -5
- 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-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 +2 -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 +1 -3
- 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 +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- 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 +5 -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 +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +0 -2
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +10 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +6 -5
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +6 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/scope.rbs +51 -50
- data/sig/rigor/type.rbs +0 -6
- 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/03-baseline-and-bugs.md +12 -1
- metadata +55 -3
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
class Environment
|
|
5
|
-
# Frozen
|
|
5
|
+
# Frozen read-only RBS query facade.
|
|
6
6
|
# [ADR-15](../../../docs/adr/15-ractor-concurrency.md) Phase 2b extracts the read-only surface of
|
|
7
7
|
# {RbsLoader} into this carrier so future Ractor-isolated workers can share one Reflection across the
|
|
8
8
|
# pool while keeping the per-Ractor mutable accelerator state (per-process memo Hashes) where it belongs.
|
|
9
9
|
#
|
|
10
10
|
# Backing tables (all frozen at construction):
|
|
11
11
|
#
|
|
12
|
-
# - `known_class_names` —
|
|
13
|
-
#
|
|
14
|
-
# - `instance_definitions` — `
|
|
12
|
+
# - `known_class_names` — every class / module / alias name in the loaded RBS environment.
|
|
13
|
+
# Top-level prefixed (`"::Hash"`); the query methods accept either form.
|
|
14
|
+
# - `instance_definitions` — `RBS::Definition` per class, keyed on `RBS::TypeName#to_s` (top-level
|
|
15
15
|
# prefixed).
|
|
16
16
|
# - `singleton_definitions` — same shape, singleton side.
|
|
17
|
-
# - `type_param_names` —
|
|
18
|
-
# - `constant_types` —
|
|
19
|
-
# - `ancestor_names` —
|
|
17
|
+
# - `type_param_names` — declared type parameters per class.
|
|
18
|
+
# - `constant_types` — translated constant declarations.
|
|
19
|
+
# - `ancestor_names` — normalised ancestor chains per class.
|
|
20
20
|
#
|
|
21
21
|
# Each `Reflection` instance is `frozen?` at construction — every cached table is frozen, `self` is
|
|
22
22
|
# frozen. **NOT** `Ractor.shareable?`: the `instance_definitions` / `singleton_definitions` tables hold
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../analysis/path_expansion"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
class Environment
|
|
@@ -42,6 +43,36 @@ module Rigor
|
|
|
42
43
|
# anything.
|
|
43
44
|
PATTERN = /(?:(?<![\w.$@])|(?<=Kernel\.))require[ \t]*\(?[ \t]*(["'])(#{FEATURE_ALTERNATION})\1/
|
|
44
45
|
|
|
46
|
+
# Issue #2174 — the gems Ruby 4.0 bundles (its `gems/bundled_gems` list, which an installation does not
|
|
47
|
+
# keep): installed beside the interpreter, but loaded only by an explicit `require`. Gems installed later
|
|
48
|
+
# into the same directory are deliberately not read from it.
|
|
49
|
+
BUNDLED_GEMS = %w[
|
|
50
|
+
abbrev base64 benchmark bigdecimal csv debug drb fiddle getoptlong irb logger matrix minitest mutex_m
|
|
51
|
+
net-ftp net-imap net-pop net-smtp nkf observer ostruct power_assert prime pstore racc rake rbs rdoc readline
|
|
52
|
+
reline repl_type_completor resolv-replace rexml rinda rss syslog test-unit typeprof win32ole
|
|
53
|
+
].freeze
|
|
54
|
+
|
|
55
|
+
# Issue #2174 — the first path segments of the features Ruby ships: the `.rb` files and directories of the
|
|
56
|
+
# interpreter's library directory, the extensions and directories of its architecture directory, and the
|
|
57
|
+
# bundled gems' names (`net-ftp` → `net`). Read once, at load time and on the main Ractor, since
|
|
58
|
+
# `RbConfig::CONFIG` is not shareable; empty when the directories cannot be read.
|
|
59
|
+
SHIPPED_SEGMENTS = begin
|
|
60
|
+
require "rbconfig"
|
|
61
|
+
segments = Set.new
|
|
62
|
+
[RbConfig::CONFIG["rubylibdir"], RbConfig::CONFIG["archdir"]].compact.each do |dir|
|
|
63
|
+
next unless File.directory?(dir)
|
|
64
|
+
|
|
65
|
+
Dir.children(dir).each { |entry| segments << entry.sub(/\.(?:rb|so|bundle|dll)\z/, "") }
|
|
66
|
+
end
|
|
67
|
+
BUNDLED_GEMS.each { |name| segments << name.split("-").first }
|
|
68
|
+
segments.to_set(&:freeze).freeze
|
|
69
|
+
rescue StandardError
|
|
70
|
+
Set.new.freeze
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# A `require` of any literal feature, read as {PATTERN} reads one; {.features_of} keeps those Ruby ships.
|
|
74
|
+
ANY_FEATURE = %r{(?:(?<![\w.$@])|(?<=Kernel\.))require[ \t]*\(?[ \t]*(["'])([\w-]+(?:/[\w-]+)*)\1}
|
|
75
|
+
|
|
45
76
|
module_function
|
|
46
77
|
|
|
47
78
|
# The features a run over `configuration` requires: every file under the configured paths, plus
|
|
@@ -53,37 +84,51 @@ module Rigor
|
|
|
53
84
|
def for_configuration(configuration, extra_files = [], sources: {})
|
|
54
85
|
files = Analysis::PathExpansion.ruby_files(configuration.paths, configuration.exclude_patterns,
|
|
55
86
|
base: configuration.exclude_base)
|
|
56
|
-
scan(files + Array(extra_files).map(&:to_s), sources: sources
|
|
87
|
+
scan(files + Array(extra_files).map(&:to_s), sources: sources,
|
|
88
|
+
sig_paths: configured_sig_paths(configuration))
|
|
57
89
|
rescue StandardError
|
|
58
90
|
[]
|
|
59
91
|
end
|
|
60
92
|
|
|
93
|
+
# The project signature roots the environment loads: the configured `signature_paths:`, else `./sig` when it
|
|
94
|
+
# exists (`Environment.project_signature_roots`, which this leaf file does not load).
|
|
95
|
+
def configured_sig_paths(configuration)
|
|
96
|
+
configured = configuration.signature_paths
|
|
97
|
+
return Array(configured).map(&:to_s) if configured
|
|
98
|
+
|
|
99
|
+
File.directory?("sig") ? ["sig"] : []
|
|
100
|
+
end
|
|
101
|
+
|
|
61
102
|
# @param files — source paths (Strings or Pathnames); an unreadable one contributes nothing.
|
|
62
103
|
# @param sources — path => source text, scanned instead of the file; its paths are scanned even when
|
|
63
104
|
# `files` does not list them.
|
|
64
|
-
# @
|
|
65
|
-
|
|
105
|
+
# @param sig_paths — the project's signature roots, whose `.rbs` declaration headers count as the project's
|
|
106
|
+
# own names ({.relevant}).
|
|
107
|
+
# @return the sorted, frozen features some file requires, a shipped one kept only when {.relevant}.
|
|
108
|
+
def scan(files, sources: {}, sig_paths: [])
|
|
66
109
|
sources = sources.to_h { |path, text| [File.expand_path(path.to_s), text] }
|
|
67
110
|
paths = (Array(files).map { |path| source_key(File.expand_path(path.to_s), sources) } + sources.keys).uniq
|
|
68
111
|
found = Set.new
|
|
112
|
+
names = Set.new
|
|
69
113
|
paths.each do |path|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
114
|
+
features, declared = sources.key?(path) ? scan_source(sources[path]) : memoized_scan_in(path)
|
|
115
|
+
found.merge(features)
|
|
116
|
+
names.merge(declared)
|
|
73
117
|
end
|
|
74
118
|
prune_memo(paths)
|
|
75
|
-
found.
|
|
119
|
+
relevant(found, names.merge(sig_names(sig_paths))).sort.freeze
|
|
76
120
|
end
|
|
77
121
|
|
|
78
|
-
# @return path (absolute) =>
|
|
79
|
-
# that
|
|
122
|
+
# @return path (absolute) => `[features, declared names]` ({.scan_source}) for every file under the
|
|
123
|
+
# configured paths that has either. A long-lived reader (the language server) keeps this, overlays its open
|
|
124
|
+
# buffers and passes the union to {.relevant}.
|
|
80
125
|
def feature_files(configuration)
|
|
81
126
|
listed = Analysis::PathExpansion.ruby_files(configuration.paths, configuration.exclude_patterns,
|
|
82
127
|
base: configuration.exclude_base)
|
|
83
128
|
files = listed.map { |path| File.expand_path(path.to_s) }
|
|
84
129
|
result = files.each_with_object({}) do |path, acc|
|
|
85
|
-
|
|
86
|
-
acc[path] =
|
|
130
|
+
scanned = memoized_scan_in(path)
|
|
131
|
+
acc[path] = scanned unless scanned.all?(&:empty?)
|
|
87
132
|
end
|
|
88
133
|
prune_memo(files)
|
|
89
134
|
result
|
|
@@ -91,6 +136,42 @@ module Rigor
|
|
|
91
136
|
{}
|
|
92
137
|
end
|
|
93
138
|
|
|
139
|
+
# Issue #2174 — `features` with each shipped feature kept only when one of `names` (constant names the
|
|
140
|
+
# project's source or signatures declare a class or module under) is a root the feature's library declares
|
|
141
|
+
# ({RubyLibraryRoots}). Only such a feature can open a class (`RbsLoader#ruby_library_reopen?` asks it of a
|
|
142
|
+
# class every declaration of which is the project's), so dropping the rest changes no diagnostic, and keeps a
|
|
143
|
+
# `require "json"` edit in a project that reopens no library class out of the incremental snapshot's
|
|
144
|
+
# fingerprint and the language server's environment key. The listed features ({VENDORED_DIRS}) are kept.
|
|
145
|
+
def relevant(features, names)
|
|
146
|
+
return features.to_a if features.all? { |feature| VENDORED_DIRS.key?(feature) }
|
|
147
|
+
|
|
148
|
+
segments = names.empty? ? EMPTY_SEGMENTS : library_segments(names)
|
|
149
|
+
features.select { |feature| VENDORED_DIRS.key?(feature) || segments.include?(feature.split("/").first) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# The feature segments of the libraries that declare one of `names` as a root. Loaded lazily: the roots read
|
|
153
|
+
# the `rbs` stdlib tree, which a run with no shipped `require` never needs.
|
|
154
|
+
def library_segments(names)
|
|
155
|
+
require_relative "ruby_library_roots"
|
|
156
|
+
roots = RubyLibraryRoots.shared
|
|
157
|
+
names.each_with_object(Set.new) { |name, acc| acc.merge(roots.fetch(name, EMPTY_SEGMENTS)) }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
EMPTY_SEGMENTS = [].freeze
|
|
161
|
+
|
|
162
|
+
# The constant names the `.rbs` files under `sig_paths` declare a class or module under.
|
|
163
|
+
def sig_names(sig_paths)
|
|
164
|
+
Array(sig_paths).each_with_object(Set.new) do |root, acc|
|
|
165
|
+
next unless File.directory?(root.to_s)
|
|
166
|
+
|
|
167
|
+
RootedGlob.glob(root.to_s, "**/*.rbs").each do |path|
|
|
168
|
+
acc.merge(declared_names(File.binread(path)))
|
|
169
|
+
rescue SystemCallError, IOError
|
|
170
|
+
next
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
94
175
|
# Paths are compared expanded, so a relative `--instead-of=lib/a.rb` replaces the configured
|
|
95
176
|
# `/project/lib/a.rb`; a source whose real path matches (a symlinked directory) replaces it too. The real
|
|
96
177
|
# path is asked only for a file that shares a source's basename, so a scan does not resolve every path.
|
|
@@ -114,8 +195,8 @@ module Rigor
|
|
|
114
195
|
# build; a long-lived language server on every rebuild), so on the main Ractor each file's answer is kept
|
|
115
196
|
# against its stat and the file is re-read only when the stat moved. A non-main Ractor may not touch the
|
|
116
197
|
# module's state and reads the file every time.
|
|
117
|
-
def
|
|
118
|
-
return
|
|
198
|
+
def memoized_scan_in(path)
|
|
199
|
+
return scan_in(path) unless Ractor.current == Ractor.main
|
|
119
200
|
|
|
120
201
|
stat = File.stat(path)
|
|
121
202
|
stamp = [stat.mtime.to_r, stat.ctime.to_r, stat.size, stat.ino]
|
|
@@ -123,13 +204,15 @@ module Rigor
|
|
|
123
204
|
cached = memo[path]
|
|
124
205
|
return cached.last if cached && cached.first == stamp
|
|
125
206
|
|
|
126
|
-
|
|
127
|
-
memo[path] = [stamp,
|
|
128
|
-
|
|
207
|
+
scanned = scan_in(path)
|
|
208
|
+
memo[path] = [stamp, scanned]
|
|
209
|
+
scanned
|
|
129
210
|
rescue SystemCallError
|
|
130
|
-
|
|
211
|
+
NOTHING
|
|
131
212
|
end
|
|
132
213
|
|
|
214
|
+
NOTHING = [[].freeze, [].freeze].freeze
|
|
215
|
+
|
|
133
216
|
# Keeps the memo to the files the latest scan was asked about, so a long-lived language server holds one
|
|
134
217
|
# entry per project file rather than one per file it ever saw.
|
|
135
218
|
def prune_memo(paths)
|
|
@@ -139,28 +222,51 @@ module Rigor
|
|
|
139
222
|
@memo.select! { |path, _| keep.include?(path) }
|
|
140
223
|
end
|
|
141
224
|
|
|
142
|
-
# @return
|
|
143
|
-
def
|
|
144
|
-
|
|
225
|
+
# @return `[features, declared names]` for one file ({.scan_source}).
|
|
226
|
+
def scan_in(path)
|
|
227
|
+
scan_source(File.binread(path))
|
|
145
228
|
rescue SystemCallError, IOError
|
|
146
|
-
|
|
229
|
+
NOTHING
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# @return `[features, declared names]`: what {.features_of} reads, and the constant names a `class` or
|
|
233
|
+
# `module` header in `source` spells, each segment on its own (`class Net::SMTP` gives `Net` and `SMTP`).
|
|
234
|
+
def scan_source(source)
|
|
235
|
+
[features_of(source), declared_names(source)].freeze
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# A header on a line of its own, or inside a comment (an rbs-inline `# @rbs!` block declares
|
|
239
|
+
# `# class Matrix`). Over-matching a comment only keeps more features.
|
|
240
|
+
HEADER = /^[ \t]*(?:#[ \t]*)?(?:class|module)[ \t]+(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)/
|
|
241
|
+
|
|
242
|
+
def declared_names(source)
|
|
243
|
+
bytes = source.b
|
|
244
|
+
return [] unless bytes.include?("class") || bytes.include?("module")
|
|
245
|
+
|
|
246
|
+
bytes.scan(HEADER).flat_map { |(name)| name.split("::") }
|
|
247
|
+
.uniq.map { |name| name.dup.force_encoding(Encoding::UTF_8).freeze }.freeze
|
|
147
248
|
end
|
|
148
249
|
|
|
149
|
-
# @return the features `source` requires
|
|
250
|
+
# @return the features `source` requires: the listed ones ({VENDORED_DIRS}), and, for issue #2174, every
|
|
251
|
+
# feature Ruby ships ({SHIPPED_SEGMENTS}), which `RbsLoader#ruby_library_reopen?` reads to tell a reopened
|
|
252
|
+
# library class from a project class of the same name. Only the listed ones become library tokens.
|
|
150
253
|
def features_of(source)
|
|
151
|
-
# A plain substring test first:
|
|
152
|
-
# the regexp on a file that
|
|
153
|
-
return [] unless
|
|
254
|
+
# A plain substring test first: a file with no `require` at all is the common case, and it is far cheaper
|
|
255
|
+
# than the regexp on a file that has none.
|
|
256
|
+
return [] unless source.include?("require")
|
|
154
257
|
|
|
155
258
|
bytes = source.b
|
|
156
259
|
named = []
|
|
157
260
|
position = 0
|
|
158
|
-
while (match =
|
|
261
|
+
while (match = ANY_FEATURE.match(bytes, position))
|
|
159
262
|
named << match[2] unless commented?(bytes, match.begin(0))
|
|
160
263
|
position = match.end(0)
|
|
161
264
|
end
|
|
162
|
-
# The table's own frozen keys, so the result is shareable across Ractors
|
|
163
|
-
|
|
265
|
+
# The table's own frozen keys, and frozen copies of the rest, so the result is shareable across Ractors
|
|
266
|
+
# whatever the source's encoding.
|
|
267
|
+
listed = VENDORED_DIRS.each_key.select { |feature| named.include?(feature) }
|
|
268
|
+
shipped = named.select { |feature| SHIPPED_SEGMENTS.include?(feature.split("/").first) }
|
|
269
|
+
(listed + shipped.map { |feature| feature.dup.force_encoding(Encoding::UTF_8).freeze }).uniq
|
|
164
270
|
end
|
|
165
271
|
|
|
166
272
|
# Whether the match at `offset` sits on a line whose first non-blank character is `#`: a whole-line comment.
|
|
@@ -176,7 +282,8 @@ module Rigor
|
|
|
176
282
|
# The library tokens for `features` and for any listed feature the configuration's `libraries:` names.
|
|
177
283
|
def tokens(features, configured_libraries)
|
|
178
284
|
configured = Array(configured_libraries).map(&:to_s).select { |name| VENDORED_DIRS.key?(name) }
|
|
179
|
-
|
|
285
|
+
listed = Array(features).select { |feature| VENDORED_DIRS.key?(feature) }
|
|
286
|
+
(listed | configured).sort.map { |feature| token(feature) }
|
|
180
287
|
end
|
|
181
288
|
|
|
182
289
|
def token(feature)
|
|
@@ -189,17 +296,14 @@ module Rigor
|
|
|
189
296
|
name.start_with?(TOKEN_PREFIX) ? name.delete_prefix(TOKEN_PREFIX) : nil
|
|
190
297
|
end
|
|
191
298
|
|
|
192
|
-
# The vendored directory basenames a library list activates.
|
|
193
299
|
def active_dirs(library_names)
|
|
194
300
|
library_names.filter_map { |name| VENDORED_DIRS[feature_of_token(name)] }
|
|
195
301
|
end
|
|
196
302
|
|
|
197
|
-
# Whether `dir_basename` is one of the gated directories.
|
|
198
303
|
def gated_dir?(dir_basename)
|
|
199
304
|
VENDORED_DIRS.value?(dir_basename)
|
|
200
305
|
end
|
|
201
306
|
|
|
202
|
-
# The feature a gated directory loads for.
|
|
203
307
|
def feature_for_dir(dir_basename)
|
|
204
308
|
VENDORED_DIRS.key(dir_basename)
|
|
205
309
|
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
require_relative "missing_gem_constant_index"
|
|
6
|
+
require_relative "required_features"
|
|
7
|
+
require_relative "../rooted_glob"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
class Environment
|
|
11
|
+
# Issue #2174 — which library Ruby ships declares each root constant name. Three sources are read, and no others:
|
|
12
|
+
#
|
|
13
|
+
# - every library of the `rbs` gem's `stdlib/` tree, loaded into this run or not, read from its `.rbs` files
|
|
14
|
+
# (`net-http` declares `Net`);
|
|
15
|
+
# - every top-level `.rb` file of the interpreter's library directory (`RbConfig::CONFIG["rubylibdir"]`), the
|
|
16
|
+
# default gems' and the standard library's entry files, read from their top-level declarations;
|
|
17
|
+
# - the entry file of each gem Ruby bundles ({RequiredFeatures::BUNDLED_GEMS}), found under the interpreter's
|
|
18
|
+
# gem directory. A gem installed there later is not one of them and is not read.
|
|
19
|
+
#
|
|
20
|
+
# Each root maps to the first path segments of the features that load it (`Matrix` → `matrix`, `Net` → `net`),
|
|
21
|
+
# which {RbsLoader#ruby_library_reopen?} intersects with the features the project's source requires
|
|
22
|
+
# ({RequiredFeatures.features_of}).
|
|
23
|
+
#
|
|
24
|
+
# Ownership is read, never guessed by camelizing a library name, for the reason {MissingGemConstantIndex}
|
|
25
|
+
# gives; that module's reader parses the `.rb` entry files. No library code runs. Everything fails open to
|
|
26
|
+
# "no library", which keeps the pre-#2174 reading. Core names (`Array`, `Kernel`) appear too, harmlessly:
|
|
27
|
+
# core signatures always load, so no project-only class carries one.
|
|
28
|
+
#
|
|
29
|
+
# The directories are resolved at load time, on the main Ractor: `RbConfig::CONFIG` and `Gem.default_dir`
|
|
30
|
+
# read non-shareable constants and raise `Ractor::IsolationError` inside a pool worker. The scan itself runs
|
|
31
|
+
# lazily (about 0.1 s), memoised per process, and only for a class about to be reported.
|
|
32
|
+
module RubyLibraryRoots
|
|
33
|
+
STDLIB_ROOT = begin
|
|
34
|
+
::RBS::Repository::DEFAULT_STDLIB_ROOT.to_s.dup.freeze
|
|
35
|
+
rescue StandardError
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
RUBY_LIB_DIR = begin
|
|
40
|
+
RbConfig::CONFIG["rubylibdir"].to_s.dup.freeze
|
|
41
|
+
rescue StandardError
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
RUBY_GEMS_DIR = begin
|
|
46
|
+
File.join(Gem.default_dir, "gems").freeze
|
|
47
|
+
rescue StandardError
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module_function
|
|
52
|
+
|
|
53
|
+
# {.build}, built once per process on the main Ractor, where the language server and the incremental
|
|
54
|
+
# snapshot fingerprint ask it repeatedly; a non-main Ractor may not keep module state and builds its own.
|
|
55
|
+
def shared
|
|
56
|
+
return build unless Ractor.current == Ractor.main
|
|
57
|
+
|
|
58
|
+
@shared ||= build
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The frozen map of root constant name to the frozen set of feature segments that load it.
|
|
62
|
+
def build
|
|
63
|
+
roots = Hash.new { |hash, key| hash[key] = Set.new }
|
|
64
|
+
stdlib_roots(roots)
|
|
65
|
+
ruby_lib_roots(roots)
|
|
66
|
+
bundled_gem_roots(roots)
|
|
67
|
+
roots.each_value(&:freeze)
|
|
68
|
+
roots.default_proc = nil
|
|
69
|
+
roots.freeze
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def stdlib_roots(roots)
|
|
73
|
+
return if STDLIB_ROOT.nil? || !File.directory?(STDLIB_ROOT)
|
|
74
|
+
|
|
75
|
+
Dir.children(STDLIB_ROOT).each do |library|
|
|
76
|
+
segment = library.split("-").first
|
|
77
|
+
RootedGlob.glob(File.join(STDLIB_ROOT, library), "*/**/*.rbs").each do |path|
|
|
78
|
+
stdlib_file_roots(path).each { |root| roots[root] << segment }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def stdlib_file_roots(path)
|
|
84
|
+
_, _, decls = ::RBS::Parser.parse_signature(File.read(path))
|
|
85
|
+
decls.filter_map { |decl| root_of(decl) if named_declaration?(decl) }
|
|
86
|
+
rescue StandardError
|
|
87
|
+
[]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def ruby_lib_roots(roots)
|
|
91
|
+
return if RUBY_LIB_DIR.nil? || !File.directory?(RUBY_LIB_DIR)
|
|
92
|
+
|
|
93
|
+
RootedGlob.glob(RUBY_LIB_DIR, "*.rb").each do |path|
|
|
94
|
+
segment = File.basename(path, ".rb")
|
|
95
|
+
MissingGemConstantIndex.top_level_root_constants(path).each { |root| roots[root] << segment }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def bundled_gem_roots(roots)
|
|
100
|
+
return if RUBY_GEMS_DIR.nil? || !File.directory?(RUBY_GEMS_DIR)
|
|
101
|
+
|
|
102
|
+
Dir.children(RUBY_GEMS_DIR).each do |basename|
|
|
103
|
+
gem_name = basename[/\A(.+?)-\d/, 1]
|
|
104
|
+
next if gem_name.nil? || !RequiredFeatures::BUNDLED_GEMS.include?(gem_name)
|
|
105
|
+
|
|
106
|
+
segment = gem_name.split("-").first
|
|
107
|
+
MissingGemConstantIndex.entry_files(File.join(RUBY_GEMS_DIR, basename), gem_name).each do |path|
|
|
108
|
+
MissingGemConstantIndex.top_level_root_constants(path).each { |root| roots[root] << segment }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def named_declaration?(decl)
|
|
114
|
+
decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def root_of(decl)
|
|
118
|
+
decl.name.to_s.delete_prefix("::").split("::").first
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
5
|
+
require_relative "gated_signature_plan"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
class Environment
|
|
9
|
+
# Issue #2470 — a `data/vendored_gem_sigs/<gem>/` member another signature source also declares stands down,
|
|
10
|
+
# so the other source's declaration binds instead of the two failing the class's definition build.
|
|
11
|
+
#
|
|
12
|
+
# The vendored directories load on `RBS::EnvironmentLoader`'s batch path, ahead of the project's
|
|
13
|
+
# `signature_paths:` (its own `sig/`, an `rbs collection install`, a gem's `sig/`). A file there that reopens
|
|
14
|
+
# a vendored class and declares one of its methods again — the activerecord 8.1 collection's `patch.rbs`
|
|
15
|
+
# writes `def async_exec` on `PG::Connection`, where the vendored `pg.rbs` has `alias async_exec exec` — made
|
|
16
|
+
# `RBS::DefinitionBuilder` raise `DuplicatedMethodDefinitionError` for the class, which Rigor keeps known, so
|
|
17
|
+
# every call into it read `Dynamic[top]`.
|
|
18
|
+
#
|
|
19
|
+
# {.build} is the #1713 member rule ({GatedSignaturePlan}) applied to the ungated directories, planned from
|
|
20
|
+
# the signature files themselves rather than from a trial build: the vendored copy is Rigor's guess at a gem's
|
|
21
|
+
# surface, and the project chose the other source, so the other source wins (`RbsLoader.build_env_for`'s
|
|
22
|
+
# stated precedence). Only a directory with a member to leave out is planned; the rest stay on the batch path.
|
|
23
|
+
# An overload continuation (`def m: ... | ...`) declares nothing of its own and keeps the vendored base.
|
|
24
|
+
#
|
|
25
|
+
# The plan is a pure function of the vendored files and the signature files, both of which the env-cache key
|
|
26
|
+
# digests, so a cold and a warm run build the same environment.
|
|
27
|
+
module VendoredSignaturePlan
|
|
28
|
+
# `dirs` — the vendored directories (Pathnames) to keep off the batch path; `sources` — their files as
|
|
29
|
+
# `[buffer, directives, decls]`, the taken members left out, to add right after the batch; `takers` — the
|
|
30
|
+
# signature files a member stood down to. A member stands down only to a file that stays in the env: the
|
|
31
|
+
# caller re-plans without each taker the build quarantined (`RbsLoader.build_env_attempt`), or the method
|
|
32
|
+
# would have no declaration at all.
|
|
33
|
+
Plan = Data.define(:dirs, :sources, :takers)
|
|
34
|
+
EMPTY = Plan.new(dirs: [].freeze, sources: [].freeze, takers: Set.new.freeze)
|
|
35
|
+
|
|
36
|
+
# A `class` / `module` header, after any `%a{...}` / `%a(...)` / `%a[...]` annotations on the same line.
|
|
37
|
+
ANNOTATED_HEADER = /^\s*(?:%a(?:\{[^}]*\}|\([^)]*\)|\[[^\]]*\])\s*)*(?:class|module)\s+(?:::)?/
|
|
38
|
+
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
# @param roots — the ungated vendored directories this build loads.
|
|
42
|
+
# @param signature_files — the absolute paths of the signature files that outrank them.
|
|
43
|
+
# @param excluded — signature files to plan without: takers a previous build of this env quarantined.
|
|
44
|
+
def build(roots, signature_files, excluded = Set.new)
|
|
45
|
+
files = signature_files.reject { |file| excluded.include?(file) }
|
|
46
|
+
return EMPTY if roots.empty? || files.empty?
|
|
47
|
+
|
|
48
|
+
parsed = roots.to_h { |root| [root, parsed_sources(root)] }
|
|
49
|
+
taken = taken_keys(files, header_pattern(parsed.values.flatten(1)))
|
|
50
|
+
return EMPTY if taken.empty?
|
|
51
|
+
|
|
52
|
+
takers = Set.new
|
|
53
|
+
planned = parsed.filter_map do |root, sources|
|
|
54
|
+
kept = plan_sources(sources, taken, takers)
|
|
55
|
+
[root, kept] if kept
|
|
56
|
+
end
|
|
57
|
+
return EMPTY if planned.empty?
|
|
58
|
+
|
|
59
|
+
Plan.new(dirs: planned.map(&:first), sources: planned.flat_map(&:last), takers: takers.freeze)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# A vendored directory's files, parsed, under the Pathname buffer names the loader's batch path gives them,
|
|
63
|
+
# in the set `RBS::EnvironmentLoader#add(path:)` reads: every `.rbs` under it.
|
|
64
|
+
def parsed_sources(root)
|
|
65
|
+
RootedGlob.glob(root, "**/*.rbs").sort.filter_map { |file| RbsLoader.parse_signature_file(Pathname(file)) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# `sources` with the taken members left out, or nil when nothing is taken from any of them.
|
|
69
|
+
def plan_sources(sources, taken, takers)
|
|
70
|
+
kept = sources.map do |buffer, directives, decls|
|
|
71
|
+
[buffer, directives, plan_decls(decls, nil, taken, takers)]
|
|
72
|
+
end
|
|
73
|
+
kept unless kept.zip(sources).all? { |(_, _, after), (_, _, before)| unchanged?(after, before) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# A header naming one of the vendored top-level types, which every declaration of a vendored type (nested or
|
|
77
|
+
# `A::B`-qualified) starts from. A pre-filter only: the matches are parsed.
|
|
78
|
+
def header_pattern(parsed)
|
|
79
|
+
heads = parsed.flat_map do |_buffer, _directives, decls|
|
|
80
|
+
decls.filter_map { |decl| GatedSignaturePlan.type_declaration?(decl) && decl.name.to_s.delete_prefix("::") }
|
|
81
|
+
end
|
|
82
|
+
segments = heads.map { |name| Regexp.escape(name.split("::").first) }.uniq
|
|
83
|
+
return nil if segments.empty?
|
|
84
|
+
|
|
85
|
+
/#{ANNOTATED_HEADER}(?:#{segments.join('|')})\b/
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# `{type name => {[method, kind] => Set[file]}}` over the signature files that declare a vendored top-level
|
|
89
|
+
# type.
|
|
90
|
+
def taken_keys(signature_files, pattern)
|
|
91
|
+
taken = Hash.new { |hash, name| hash[name] = Hash.new { |keys, key| keys[key] = Set.new } }
|
|
92
|
+
return taken if pattern.nil?
|
|
93
|
+
|
|
94
|
+
signature_files.sort.each do |file|
|
|
95
|
+
text = RbsLoader.read_signature_text(file)
|
|
96
|
+
next if text.nil? || !text.match?(pattern)
|
|
97
|
+
|
|
98
|
+
decls = RbsLoader.parse_signature_source(file, text)
|
|
99
|
+
collect_keys(decls, nil, taken, file) if decls
|
|
100
|
+
end
|
|
101
|
+
taken
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def collect_keys(decls, namespace, taken, file)
|
|
105
|
+
decls.each do |decl|
|
|
106
|
+
next unless GatedSignaturePlan.type_declaration?(decl)
|
|
107
|
+
|
|
108
|
+
name = GatedSignaturePlan.qualify(namespace, decl.name)
|
|
109
|
+
decl.members.each do |member|
|
|
110
|
+
RbsLoader.member_method_keys(member).each { |key| taken[name][key] << file }
|
|
111
|
+
end
|
|
112
|
+
collect_keys(decl.members, name, taken, file)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# `decls` with every member `taken` declares for its type left out, nested types planned the same way, and
|
|
117
|
+
# the files each left-out member stood down to added to `takers`. A declaration nothing is taken from is
|
|
118
|
+
# returned as itself, which is how {.build} tells a directory unchanged.
|
|
119
|
+
def plan_decls(decls, namespace, taken, takers)
|
|
120
|
+
decls.map do |decl|
|
|
121
|
+
next decl unless GatedSignaturePlan.type_declaration?(decl)
|
|
122
|
+
|
|
123
|
+
plan_type(decl, GatedSignaturePlan.qualify(namespace, decl.name), taken, takers)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def unchanged?(after, before)
|
|
128
|
+
after.size == before.size && after.zip(before).all? { |a, b| a.equal?(b) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def plan_type(decl, name, taken, takers)
|
|
132
|
+
keys = taken.fetch(name, nil)
|
|
133
|
+
members = decl.members.filter_map do |member|
|
|
134
|
+
if GatedSignaturePlan.type_declaration?(member)
|
|
135
|
+
plan_type(member, GatedSignaturePlan.qualify(name, member.name), taken, takers)
|
|
136
|
+
elsif keys
|
|
137
|
+
plan_member(member, keys, takers)
|
|
138
|
+
else
|
|
139
|
+
member
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
return decl if unchanged?(members, decl.members)
|
|
143
|
+
|
|
144
|
+
GatedSignaturePlan.rebuild(decl, members: members)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def plan_member(member, keys, takers)
|
|
148
|
+
RbsLoader.member_method_keys(member).each { |key| takers.merge(keys[key]) if keys.key?(key) }
|
|
149
|
+
GatedSignaturePlan.untaken(member, keys)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|