rigortype 0.3.7 → 0.3.9
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 +1 -1
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +37 -4
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +14 -1
- data/docs/handbook/11-sig-gen.md +96 -15
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +39 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +12 -1
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +6 -1
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/plugins/rigor-actionpack.md +18 -0
- data/docs/manual/plugins/rigor-activerecord.md +28 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
- data/lib/rigor/analysis/baseline.rb +2 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +200 -32
- data/lib/rigor/analysis/crash_signature.rb +69 -13
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +6 -1
- data/lib/rigor/analysis/incremental.rb +16 -0
- data/lib/rigor/analysis/incremental_session.rb +100 -29
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +4 -1
- data/lib/rigor/analysis/run_cache_key.rb +1 -1
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +316 -40
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +430 -77
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
- data/lib/rigor/analysis/runner.rb +200 -43
- data/lib/rigor/analysis/worker_session.rb +36 -12
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +166 -30
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +4 -4
- data/lib/rigor/builtins/static_return_refinements.rb +5 -6
- data/lib/rigor/cache/annotation_location.rb +70 -0
- data/lib/rigor/cache/descriptor.rb +58 -20
- data/lib/rigor/cache/engine_source.rb +4 -4
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +40 -6
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +79 -11
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +39 -0
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +85 -33
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +3 -3
- data/lib/rigor/cli/docs_command.rb +2 -2
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +1 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +2 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +31 -2
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +36 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +10 -6
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +68 -6
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -6
- data/lib/rigor/configuration.rb +43 -4
- data/lib/rigor/effects/attribution.rb +1 -3
- data/lib/rigor/effects/config_envelopes.rb +6 -8
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +0 -1
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +8 -8
- data/lib/rigor/effects/envelope_index.rb +5 -8
- data/lib/rigor/effects/framework_units.rb +4 -6
- data/lib/rigor/effects/identity.rb +4 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/method_key.rb +1 -1
- data/lib/rigor/effects/plugin_facts.rb +4 -6
- data/lib/rigor/effects/propagator.rb +2 -3
- data/lib/rigor/effects/scanner.rb +1 -1
- data/lib/rigor/effects/signature_sources.rb +3 -5
- data/lib/rigor/effects/snapshot.rb +10 -10
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/unit_scan.rb +10 -10
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +28 -0
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +51 -4
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +613 -113
- data/lib/rigor/environment.rb +189 -27
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/inference/acceptance.rb +153 -8
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +5 -5
- data/lib/rigor/inference/budget_trace.rb +1 -5
- data/lib/rigor/inference/captured_locals.rb +2 -3
- data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +168 -0
- data/lib/rigor/inference/expression_typer.rb +763 -143
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +19 -10
- data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
- data/lib/rigor/inference/index_write_widening.rb +1 -5
- data/lib/rigor/inference/macro_block_self_type.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +12 -13
- data/lib/rigor/inference/method_parameter_binder.rb +4 -6
- data/lib/rigor/inference/multi_target_binder.rb +1 -3
- data/lib/rigor/inference/mutation_rejoin.rb +168 -0
- data/lib/rigor/inference/mutation_widening.rb +100 -51
- data/lib/rigor/inference/narrowing.rb +299 -46
- data/lib/rigor/inference/optimistic_origin.rb +1 -7
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +3 -3
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +3 -3
- data/lib/rigor/inference/refinement_mutation.rb +72 -0
- data/lib/rigor/inference/scope_indexer.rb +688 -151
- data/lib/rigor/inference/statement_evaluator.rb +221 -50
- data/lib/rigor/inference/string_mutation.rb +60 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +1 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +2 -2
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +2 -5
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/plugin/base.rb +90 -21
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +7 -7
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +31 -12
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +23 -1
- data/lib/rigor/plugin/registry.rb +67 -14
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +11 -11
- data/lib/rigor/protection/kill_signature.rb +2 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +6 -9
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
- data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
- data/lib/rigor/rbs_extended/reporter.rb +122 -16
- data/lib/rigor/rbs_extended.rb +67 -22
- data/lib/rigor/reflection/constant_path.rb +126 -0
- data/lib/rigor/reflection.rb +26 -10
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +30 -0
- data/lib/rigor/scope.rb +203 -4
- data/lib/rigor/sig_gen/generator.rb +181 -102
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/observation_collector.rb +17 -15
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +17 -11
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/writer.rb +23 -6
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +2 -5
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +68 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +5 -5
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +3 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/environment.rbs +11 -0
- data/sig/rigor/inference.rbs +3 -10
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/manifest.rbs +2 -1
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +24 -3
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
- data/sig/rigor/type.rbs +27 -1
- data/sig/rigor.rbs +1 -0
- data/skills/rigor-ask/SKILL.md +8 -6
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
- data/skills/rigor-project-init/SKILL.md +6 -3
- data/skills/rigor-project-init/references/02-configure.md +10 -6
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-type-oracle/SKILL.md +228 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
- metadata +28 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -84,11 +84,24 @@ module Rigor
|
|
|
84
84
|
# sigs are Rigor-shipped and trusted, so they stay on the loader's fast batch path.
|
|
85
85
|
add_bundled_signatures(rbs_loader, loaded_libraries.to_set(&:to_s))
|
|
86
86
|
env = RBS::Environment.from_loader(rbs_loader)
|
|
87
|
+
project_files = project_sig_files(signature_paths)
|
|
87
88
|
add_project_signatures(env, signature_paths, deferred_signature_paths)
|
|
88
|
-
|
|
89
|
+
# Issue #928 — the capability-role catalog goes in AFTER the project's own signatures, per
|
|
90
|
+
# declaration, so a project that already declares one of these interface names keeps its own
|
|
91
|
+
# (`add_capability_role_signatures`). The rest of the bundled RBS cannot be ordered that way (it
|
|
92
|
+
# is what `from_loader` builds the env FROM), which is why a colliding project file is quarantined
|
|
93
|
+
# instead — and losing a whole `sig/` file to a name as ordinary as `_Closable` is exactly what
|
|
94
|
+
# shipping a top-level interface catalog would otherwise cost.
|
|
95
|
+
add_capability_role_signatures(env)
|
|
96
|
+
# Issue #824 — the project's own signatures are already in `env` at this point, so the inline
|
|
97
|
+
# contribution can be asked the question that keeps the two from colliding: which of its members
|
|
98
|
+
# does a `.rbs` file already declare? Those stand down per member ({.add_virtual_rbs}).
|
|
99
|
+
add_virtual_rbs(env, virtual_rbs, sig_member_owners: signature_member_owners(project_files, virtual_rbs))
|
|
89
100
|
synthesize_missing_namespaces(env)
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
# Issue #777 — resolve-time backstop: also unload colliding PROJECT buffers (class-vs-module /
|
|
102
|
+
# constant redeclarations against bundled RBS). Virtual culprits are preferred when both appear.
|
|
103
|
+
env, resolved = resolve_quarantining_virtual_collisions(env, virtual_rbs, project_files: project_files)
|
|
104
|
+
stub_missing_referenced_types(env, resolved, project_files)
|
|
92
105
|
end
|
|
93
106
|
|
|
94
107
|
# rbs ships `stdlib/bigdecimal/` and `stdlib/bigdecimal-math/` as two libraries, so
|
|
@@ -116,8 +129,8 @@ module Rigor
|
|
|
116
129
|
BIG_MATH_SIG_BASENAME = "big_math.rbs"
|
|
117
130
|
private_constant :BIG_MATH_SIG_BASENAME
|
|
118
131
|
|
|
119
|
-
# @param libraries
|
|
120
|
-
# @return
|
|
132
|
+
# @param libraries — the resolved library list, `DEFAULT_LIBRARIES` included.
|
|
133
|
+
# @return the same list, minus `bigdecimal-math` when `bigdecimal` already brings
|
|
121
134
|
# `BigMath` in.
|
|
122
135
|
def libraries_without_shadowed_bigdecimal_math(libraries)
|
|
123
136
|
return libraries unless libraries.include?(BIGDECIMAL_LIBRARY) && libraries.include?(BIGDECIMAL_MATH_LIBRARY)
|
|
@@ -157,32 +170,61 @@ module Rigor
|
|
|
157
170
|
false
|
|
158
171
|
end
|
|
159
172
|
|
|
160
|
-
# Backstop for a
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
# range spans `>= 3.0, < 5.0` (ADR-79) and WHERE duplicate detection fires is an
|
|
164
|
-
# this code must not depend on. Resolution
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
# `#unload` (rbs 3.x), re-raises into
|
|
173
|
+
# Backstop for a `RBS::DuplicatedDeclarationError` that only materialises at `resolve_type_names`
|
|
174
|
+
# (which rebuilds the env from `sources`). {.add_virtual_rbs} / {.add_project_parsed_decls}
|
|
175
|
+
# transactionally handle the add-time case — empirically everything on rbs 4.x — but the rbs
|
|
176
|
+
# gemspec range spans `>= 3.0, < 5.0` (ADR-79) and WHERE duplicate detection fires is an
|
|
177
|
+
# rbs-internal choice this code must not depend on. Resolution preference: drop colliding VIRTUAL
|
|
178
|
+
# buffers first (explicit `.rbs` wins), else drop colliding PROJECT `signature_paths:` buffers
|
|
179
|
+
# (issue #777 — bundled RBS wins over a kind-colliding project file), then retry. Every pass
|
|
180
|
+
# removes at least one buffer, so the loop is bounded. A duplicate with neither virtual nor
|
|
181
|
+
# project culprits (bundled-vs-bundled), or an env without `#unload` (rbs 3.x), re-raises into
|
|
182
|
+
# the existing one-warning total-failure path.
|
|
169
183
|
#
|
|
170
184
|
# The dropped set is not returned: consumers recover it from the built env via
|
|
171
185
|
# {#virtual_rbs_collision_quarantined}, which also works on a cache HIT where this build never ran.
|
|
172
|
-
def resolve_quarantining_virtual_collisions(env, virtual_rbs)
|
|
186
|
+
def resolve_quarantining_virtual_collisions(env, virtual_rbs, project_files: [])
|
|
173
187
|
virtual_names = virtual_rbs.to_set { |name, _content| name.to_s }
|
|
174
|
-
|
|
188
|
+
project_names = Array(project_files).to_set(&:to_s)
|
|
189
|
+
# Bound by virtual + project culprit candidates: each pass removes at least one buffer.
|
|
190
|
+
(virtual_names.size + project_names.size + 1).times do
|
|
175
191
|
return [env, env.resolve_type_names]
|
|
176
192
|
rescue ::RBS::DuplicatedDeclarationError => e
|
|
177
|
-
|
|
193
|
+
env = unload_duplicated_declaration_culprits(
|
|
194
|
+
env, e, virtual_names: virtual_names, project_names: project_names
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
[env, env.resolve_type_names]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Prefer dropping colliding VIRTUAL buffers (explicit `.rbs` wins), else PROJECT `signature_paths:`
|
|
201
|
+
# buffers (issue #777 — bundled RBS wins over a kind-colliding project file). A duplicate with
|
|
202
|
+
# neither virtual nor project culprits (bundled-vs-bundled), or an env without `#unload`, re-raises.
|
|
203
|
+
def unload_duplicated_declaration_culprits(env, error, virtual_names:, project_names:)
|
|
204
|
+
raise unless env.respond_to?(:unload)
|
|
205
|
+
|
|
206
|
+
culprits = duplicated_declaration_buffer_names(error)
|
|
207
|
+
virtual_culprits = culprits.select { |name| virtual_names.include?(name.to_s) }
|
|
208
|
+
return env.unload(virtual_culprits) unless virtual_culprits.empty?
|
|
178
209
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
210
|
+
# Prefer dropping the project buffer(s): the bundled declaration stays, Greeter/core stay
|
|
211
|
+
# usable, and only the conflicting file is absent.
|
|
212
|
+
project_culprits = select_project_collision_culprits(culprits, project_names)
|
|
213
|
+
raise if project_culprits.empty?
|
|
182
214
|
|
|
183
|
-
|
|
215
|
+
env.unload(project_culprits)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def duplicated_declaration_buffer_names(error)
|
|
219
|
+
error.decls.filter_map { |decl| decl.location&.buffer&.name }.uniq
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def select_project_collision_culprits(culprits, project_names)
|
|
223
|
+
culprits.select do |name|
|
|
224
|
+
project_names.include?(File.expand_path(name.to_s))
|
|
225
|
+
rescue ArgumentError, TypeError
|
|
226
|
+
project_names.include?(name.to_s)
|
|
184
227
|
end
|
|
185
|
-
[env, env.resolve_type_names]
|
|
186
228
|
end
|
|
187
229
|
|
|
188
230
|
# ADR-5 robustness, second tier. A project `signature_paths:` RBS that *references* a type no loaded
|
|
@@ -298,11 +340,26 @@ module Rigor
|
|
|
298
340
|
next if parsed.nil? # quarantined (unparseable) or unreadable — skip so the env survives
|
|
299
341
|
|
|
300
342
|
buffer, directives, decls = parsed
|
|
301
|
-
|
|
343
|
+
add_project_parsed_decls(env, buffer, directives, decls)
|
|
302
344
|
end
|
|
303
345
|
add_deferred_signatures(env, deferred)
|
|
304
346
|
end
|
|
305
347
|
|
|
348
|
+
# Issue #777 — add one project signature transactionally. `RBS::Environment#add_source` appends to
|
|
349
|
+
# `sources` BEFORE inserting decls, so a mid-insert `RBS::DuplicatedDeclarationError` (class-vs-module
|
|
350
|
+
# kind collision against bundled RBS, or a constant redeclared from core) would otherwise leave a
|
|
351
|
+
# poisoned source that `resolve_type_names` re-raises outside every per-file rescue, collapsing the
|
|
352
|
+
# WHOLE env to nil. Mirror {.add_virtual_rbs}: drop the poisoned source and continue so Greeter /
|
|
353
|
+
# core / every non-conflicting project file still load. Without a `sources` table (rbs 3.x) re-raise
|
|
354
|
+
# into the existing total-failure path — we cannot make the skip transactional there.
|
|
355
|
+
def add_project_parsed_decls(env, buffer, directives, decls)
|
|
356
|
+
add_parsed_decls(env, buffer, directives, decls)
|
|
357
|
+
rescue ::RBS::DuplicatedDeclarationError
|
|
358
|
+
raise unless env.respond_to?(:sources)
|
|
359
|
+
|
|
360
|
+
env.sources.reject! { |source| source.buffer.name == buffer.name }
|
|
361
|
+
end
|
|
362
|
+
|
|
306
363
|
# Issue #610 — the DEFERRED half: signature files a bundled plugin contributes through its manifest,
|
|
307
364
|
# added after every other source so each can be asked a question the eager half cannot be asked —
|
|
308
365
|
# "does something already declare this class, at a different generic arity?".
|
|
@@ -331,12 +388,12 @@ module Rigor
|
|
|
331
388
|
buffer, directives, decls = parsed
|
|
332
389
|
next if generic_arity_conflict(env, decls)
|
|
333
390
|
|
|
334
|
-
|
|
391
|
+
add_project_parsed_decls(env, buffer, directives, decls)
|
|
335
392
|
end
|
|
336
393
|
end
|
|
337
394
|
|
|
338
395
|
# Issue #610 — which deferred (plugin-contributed) files stood down, as
|
|
339
|
-
# `[absolute_path, class_name, existing_arity, incoming_arity]`, sorted. DERIVED from a built env
|
|
396
|
+
# `[absolute_path, class_name, existing_arity, incoming_arity, existing_file]`, sorted. DERIVED from a built env
|
|
340
397
|
# rather than recorded during the build, for {RbsLoader#quarantined_signatures}' reason: a cache HIT
|
|
341
398
|
# never runs the build, and a condition only the build knew would silently disappear on the second
|
|
342
399
|
# run. Re-asking the same question of the FINAL env gives the same answer — the file's declaration is
|
|
@@ -353,21 +410,40 @@ module Rigor
|
|
|
353
410
|
end
|
|
354
411
|
end
|
|
355
412
|
|
|
356
|
-
# The first `[class_name, existing_arity, incoming_arity]` where `decls` would
|
|
357
|
-
# env already has with a DIFFERENT number of type parameters, or nil when
|
|
358
|
-
# Equal arity is not a conflict: RBS reopens the class, which is the ordinary
|
|
359
|
-
# the stand-down must not fire on it.
|
|
413
|
+
# The first `[class_name, existing_arity, incoming_arity, existing_file]` where `decls` would
|
|
414
|
+
# re-declare a class the env already has with a DIFFERENT number of type parameters, or nil when
|
|
415
|
+
# they agree everywhere. Equal arity is not a conflict: RBS reopens the class, which is the ordinary
|
|
416
|
+
# and supported case, so the stand-down must not fire on it.
|
|
417
|
+
#
|
|
418
|
+
# The existing arity is read off the entry's FIRST declaration, never through `entry.type_params`:
|
|
419
|
+
# rbs 4.x's `ClassEntry#type_params` validates every declaration against the first before answering
|
|
420
|
+
# and raises `GenericParameterMismatchError` on an entry that already disagrees with itself (rbs
|
|
421
|
+
# 3.x's does not), so the derived reader ({.deferred_standdowns}) raised on exactly the env it exists
|
|
422
|
+
# to describe — one a user's own two sources collided, or one the cache served from before the
|
|
423
|
+
# producer passed the deferred list. The first declaration is also the one rbs compares against.
|
|
424
|
+
# `existing_file` is that declaration's buffer name — the source that displaced the plugin's — or
|
|
425
|
+
# nil when it has none or carries only the cached-position sentinel.
|
|
360
426
|
def generic_arity_conflict(env, decls)
|
|
361
427
|
each_declared_class(decls) do |name, arity|
|
|
362
428
|
entry = env.class_decls[::RBS::TypeName.parse(name)]
|
|
363
|
-
next if entry.nil?
|
|
429
|
+
next if entry.nil?
|
|
364
430
|
|
|
365
|
-
|
|
366
|
-
|
|
431
|
+
first = entry_declarations(entry).first
|
|
432
|
+
existing = first.respond_to?(:type_params) ? first.type_params&.size : nil
|
|
433
|
+
return [name, existing, arity, declaration_buffer_name(first)] if existing && existing != arity
|
|
367
434
|
end
|
|
368
435
|
nil
|
|
369
436
|
end
|
|
370
437
|
|
|
438
|
+
def declaration_buffer_name(decl)
|
|
439
|
+
location = decl.respond_to?(:location) ? decl.location : nil
|
|
440
|
+
buffer = location&.buffer
|
|
441
|
+
name = buffer.nil? ? nil : buffer.name.to_s
|
|
442
|
+
return nil if name.nil? || name.empty? || name == CACHED_LOCATION_BUFFER_NAME
|
|
443
|
+
|
|
444
|
+
name
|
|
445
|
+
end
|
|
446
|
+
|
|
371
447
|
# Yields `[absolute_class_name, type_param_count]` for every class declared anywhere in `decls`,
|
|
372
448
|
# descending through modules and nested classes. Names inside a `module`/`class` body are parsed
|
|
373
449
|
# RELATIVE, so the enclosing path is accumulated here — the env is keyed by absolute name, and
|
|
@@ -436,6 +512,55 @@ module Rigor
|
|
|
436
512
|
end
|
|
437
513
|
end
|
|
438
514
|
|
|
515
|
+
# Issue #777 — project `signature_paths:` files that PARSE but are absent from the built env because
|
|
516
|
+
# {.add_project_parsed_decls} / {.resolve_quarantining_virtual_collisions} quarantined a duplicated
|
|
517
|
+
# declaration (typically class-vs-module or a constant already shipped by bundled RBS). Derived from
|
|
518
|
+
# the env like {#virtual_rbs_collision_quarantined}, so a cache HIT reports the same condition.
|
|
519
|
+
#
|
|
520
|
+
# Path membership alone is not enough: the suite's {RbsEnvMemo} reuses a byte-identical env across
|
|
521
|
+
# `Dir.mktmpdir` roots when `signature_paths:` are relative (`sig/sink.rbs`), leaving buffers named
|
|
522
|
+
# after the FIRST path. A later example's absolute path is then absent from `present` even though
|
|
523
|
+
# the declarations loaded — that used to spam `duplicated declaration against bundled RBS` banners
|
|
524
|
+
# (~60 per CI shard) for fixtures that never collided. Basename + content equality catches that
|
|
525
|
+
# reuse without hiding a real quarantine (e.g. project `class Base64` vs bundled `module Base64`,
|
|
526
|
+
# same basename, different bytes).
|
|
527
|
+
COLLISION_QUARANTINE_NOTE =
|
|
528
|
+
"duplicated declaration against bundled RBS — quarantined so the rest of the RBS env still loads"
|
|
529
|
+
|
|
530
|
+
def collision_quarantined_project_signatures(env, signature_paths)
|
|
531
|
+
return [] if env.nil?
|
|
532
|
+
|
|
533
|
+
present = env.buffers.to_set do |buffer|
|
|
534
|
+
name = buffer.name
|
|
535
|
+
next name.to_s unless name
|
|
536
|
+
|
|
537
|
+
File.expand_path(name.to_s)
|
|
538
|
+
rescue ArgumentError, TypeError
|
|
539
|
+
name.to_s
|
|
540
|
+
end
|
|
541
|
+
buffers_by_basename = env.buffers.group_by { |buffer| File.basename(buffer.name.to_s) }
|
|
542
|
+
project_sig_files(signature_paths).sort.filter_map do |file|
|
|
543
|
+
next if present.include?(file)
|
|
544
|
+
next if parse_signature_file(file).nil? # parse / encoding quarantine owns these
|
|
545
|
+
next if project_signature_loaded_under_other_path?(file, buffers_by_basename)
|
|
546
|
+
|
|
547
|
+
[file, "#{file}: #{COLLISION_QUARANTINE_NOTE}"]
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# True when `file`'s bytes are already in `env` under a different absolute path (same basename).
|
|
552
|
+
# See {#collision_quarantined_project_signatures} — keeps memoised env reuse from looking like a
|
|
553
|
+
# collision quarantine.
|
|
554
|
+
def project_signature_loaded_under_other_path?(file, buffers_by_basename)
|
|
555
|
+
peers = buffers_by_basename[File.basename(file)]
|
|
556
|
+
return false if peers.nil? || peers.empty?
|
|
557
|
+
|
|
558
|
+
content = File.read(file, encoding: "UTF-8")
|
|
559
|
+
peers.any? { |buffer| buffer.content == content }
|
|
560
|
+
rescue Errno::ENOENT, Errno::EISDIR, Errno::EACCES
|
|
561
|
+
false
|
|
562
|
+
end
|
|
563
|
+
|
|
439
564
|
# The `::`-stripped names of every type a PROJECT signature references that no loaded declaration
|
|
440
565
|
# provides — the input to {.append_stub_declarations}.
|
|
441
566
|
#
|
|
@@ -702,7 +827,12 @@ module Rigor
|
|
|
702
827
|
# provenance (RBS parse errors cite it) — it is not a real file path. Per WD6 the synthesizer-emit
|
|
703
828
|
# path is responsible for catching its own parse errors and returning `nil` rather than garbage; this
|
|
704
829
|
# method assumes its input is parseable and only rescues `RBS::ParsingError` as a fail-soft.
|
|
705
|
-
|
|
830
|
+
# @param sig_member_owners — issue #824 — `{[class, method, kind] => signature_path}`
|
|
831
|
+
# from {.signature_member_owners}. Every member it names is removed from the inline contribution
|
|
832
|
+
# before the declaration is added, so `sig/` wins per MEMBER instead of the two colliding and
|
|
833
|
+
# costing the whole class its method surface (ADR-32 WD13). The stand-down is reported, never
|
|
834
|
+
# silent: {RbsLoader#inline_member_standdowns} re-derives the same set for the run's `:info` row.
|
|
835
|
+
def add_virtual_rbs(env, virtual_rbs, sig_member_owners: {})
|
|
706
836
|
return if virtual_rbs.nil? || virtual_rbs.empty?
|
|
707
837
|
|
|
708
838
|
virtual_rbs.each do |filename, content|
|
|
@@ -713,6 +843,7 @@ module Rigor
|
|
|
713
843
|
|
|
714
844
|
buffer = ::RBS::Buffer.new(name: filename.to_s, content: content.to_s)
|
|
715
845
|
_, directives, decls = ::RBS::Parser.parse_signature(buffer)
|
|
846
|
+
strip_members_declared_in_signatures!(decls, sig_member_owners) unless sig_member_owners.empty?
|
|
716
847
|
add_parsed_decls(env, buffer, directives, decls)
|
|
717
848
|
rescue ::RBS::BaseError
|
|
718
849
|
# WD6 fail-soft: a single broken virtual RBS contribution does not pull the whole env down — for
|
|
@@ -732,6 +863,187 @@ module Rigor
|
|
|
732
863
|
end
|
|
733
864
|
end
|
|
734
865
|
|
|
866
|
+
# Issue #824 / ADR-32 WD13 — the member-level half of inline-vs-`sig/` precedence.
|
|
867
|
+
#
|
|
868
|
+
# A method declared BOTH by a project `.rbs` and by an inline annotation in its own `.rb` lands twice
|
|
869
|
+
# in one `RBS::Environment`: rbs merges the two sources into a single `ClassEntry` and ranks neither,
|
|
870
|
+
# so `RBS::DefinitionBuilder::MethodBuilder::Methods#validate!` raises
|
|
871
|
+
# `RBS::DuplicatedMethodDefinitionError` and the WHOLE class loses its method surface — every call on
|
|
872
|
+
# it, real methods and typos alike, reads `Dynamic[top]`. Measured on Rigor's own tree while
|
|
873
|
+
# [#779](https://github.com/rigortype/rigor/pull/779) was evaluated: 17 of 234 annotated files
|
|
874
|
+
# overlapped `sig/`, and the collision took 44 classes down. Steep 2.1 / rbs 4.1 have no precedence
|
|
875
|
+
# rule to follow here (Steep reports the same collision as a signature diagnostic and the class still
|
|
876
|
+
# fails to build), so the spec's "SHOULD follow Steep 2.0" pointer resolves to nothing and the choice
|
|
877
|
+
# is Rigor's: the reviewed, `sig-gen`-managed `.rbs` wins, per member.
|
|
878
|
+
#
|
|
879
|
+
# @return `{[class_name, method_name, kind] => signature_path}` for every
|
|
880
|
+
# method-shaped member the project's own `signature_paths:` files declare and the inline sources
|
|
881
|
+
# also name. Empty — and free — for a project that contributes no inline RBS, which is every
|
|
882
|
+
# project without a `source_rbs_synthesizer:` plugin.
|
|
883
|
+
#
|
|
884
|
+
# The parse is scoped by a substring pre-filter over each file's text: a declaration has to spell its
|
|
885
|
+
# own name, so a signature file mentioning none of the names the inline sources declare cannot
|
|
886
|
+
# collide with them. The filter over-approximates (it matches a name in a comment or a type position
|
|
887
|
+
# too), so it can only cost an unnecessary parse, never miss a collision.
|
|
888
|
+
def signature_member_owners(project_files, virtual_rbs)
|
|
889
|
+
return {} if project_files.nil? || virtual_rbs.nil? || virtual_rbs.empty?
|
|
890
|
+
|
|
891
|
+
names = inline_declared_name_segments(virtual_rbs)
|
|
892
|
+
return {} if names.empty?
|
|
893
|
+
|
|
894
|
+
project_files.to_a.sort.each_with_object({}) do |file, owners|
|
|
895
|
+
decls = parse_signature_file_if_mentions(file, names)
|
|
896
|
+
next if decls.nil?
|
|
897
|
+
|
|
898
|
+
each_declared_member(decls) do |class_name, member|
|
|
899
|
+
member_method_keys(member).each do |method_name, kind|
|
|
900
|
+
owners[[class_name, method_name, kind]] ||= file
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
# The `[class, method, kind, signature_path, virtual_buffer_name]` record for every inline member
|
|
907
|
+
# {.add_virtual_rbs} stands down, sorted and deduplicated.
|
|
908
|
+
#
|
|
909
|
+
# Derived from the loader's own INPUTS — the synthesized sources and the project's signature files —
|
|
910
|
+
# never from the built environment, for {RbsLoader#quarantined_signatures}' reason turned inside out:
|
|
911
|
+
# a cache HIT never runs the build, and the stripped member leaves no trace in the environment to
|
|
912
|
+
# read back. The same two inputs answer identically warm and cold, which is what makes the reported
|
|
913
|
+
# set independent of cache state.
|
|
914
|
+
def inline_member_standdowns_for(signature_paths:, virtual_rbs:)
|
|
915
|
+
owners = signature_member_owners(project_sig_files(signature_paths), virtual_rbs)
|
|
916
|
+
return [].freeze if owners.empty?
|
|
917
|
+
|
|
918
|
+
records = virtual_rbs.flat_map do |name, content|
|
|
919
|
+
next [] if content.nil? || content.empty? || invalid_encoding?(content.to_s)
|
|
920
|
+
|
|
921
|
+
decls = parse_signature_source(name, content.to_s)
|
|
922
|
+
decls.nil? ? [] : member_standdowns_in(name, decls, owners)
|
|
923
|
+
end
|
|
924
|
+
records.uniq.sort_by { |record| [record[0], record[1].to_s, record[2].to_s] }.freeze
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
def member_standdowns_in(virtual_name, decls, owners)
|
|
928
|
+
records = []
|
|
929
|
+
each_declared_member(decls) do |class_name, member|
|
|
930
|
+
member_method_keys(member).each do |method_name, kind|
|
|
931
|
+
signature_path = owners[[class_name, method_name, kind]]
|
|
932
|
+
next if signature_path.nil?
|
|
933
|
+
|
|
934
|
+
records << [class_name, method_name, kind, signature_path, virtual_name.to_s].freeze
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
records
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
# Removes, in place, every member `owners` says a project signature already declares. Nested
|
|
941
|
+
# declarations are descended into after the rejection, and a declaration member contributes no method
|
|
942
|
+
# key, so nesting is never itself removed.
|
|
943
|
+
def strip_members_declared_in_signatures!(decls, owners, prefix = [])
|
|
944
|
+
Array(decls).each do |decl|
|
|
945
|
+
next unless declaration_with_members?(decl)
|
|
946
|
+
|
|
947
|
+
inner = prefix + [decl.name.to_s.delete_prefix("::")]
|
|
948
|
+
class_name = inner.join("::")
|
|
949
|
+
decl.members.reject! do |member|
|
|
950
|
+
member_method_keys(member).any? { |method_name, kind| owners.key?([class_name, method_name, kind]) }
|
|
951
|
+
end
|
|
952
|
+
strip_members_declared_in_signatures!(decl.members, owners, inner)
|
|
953
|
+
end
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
# Yields `[class_name, member]` for every member of every class / module declaration in `decls`,
|
|
957
|
+
# descending through nesting. The enclosing path is accumulated and the name rendered `::`-stripped,
|
|
958
|
+
# so a `module Foo; class Bar` and a `class Foo::Bar` elsewhere key the same — which is what makes a
|
|
959
|
+
# `sig/` declaration and an inline one comparable at all.
|
|
960
|
+
def each_declared_member(decls, prefix = [], &block)
|
|
961
|
+
Array(decls).each do |decl|
|
|
962
|
+
next unless declaration_with_members?(decl)
|
|
963
|
+
|
|
964
|
+
inner = prefix + [decl.name.to_s.delete_prefix("::")]
|
|
965
|
+
class_name = inner.join("::")
|
|
966
|
+
decl.members.each { |member| block.call(class_name, member) }
|
|
967
|
+
each_declared_member(decl.members, inner, &block)
|
|
968
|
+
end
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
def declaration_with_members?(decl)
|
|
972
|
+
(decl.is_a?(::RBS::AST::Declarations::Module) || decl.is_a?(::RBS::AST::Declarations::Class)) &&
|
|
973
|
+
decl.respond_to?(:members)
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
# The `[method_name, kind]` pairs one RBS member contributes to a definition build, mirroring
|
|
977
|
+
# `RBS::DefinitionBuilder::MethodBuilder#build_instance` / `#build_singleton` — which is what decides
|
|
978
|
+
# whether two members collide. An attribute contributes its reader and / or writer name (`foo`,
|
|
979
|
+
# `foo=`), an alias its new name, and `def self?.x` both sides. An `overloading?` member (`def x: ...
|
|
980
|
+
# | ...`) contributes NOTHING: rbs files those under `overloads` rather than `originals`, so they are
|
|
981
|
+
# designed to compose with an existing declaration and must not stand down against it.
|
|
982
|
+
def member_method_keys(member)
|
|
983
|
+
case member
|
|
984
|
+
when ::RBS::AST::Members::MethodDefinition then method_definition_keys(member)
|
|
985
|
+
when ::RBS::AST::Members::AttrReader then [[member.name, member.kind]]
|
|
986
|
+
when ::RBS::AST::Members::AttrWriter then [[:"#{member.name}=", member.kind]]
|
|
987
|
+
when ::RBS::AST::Members::AttrAccessor then [[member.name, member.kind], [:"#{member.name}=", member.kind]]
|
|
988
|
+
when ::RBS::AST::Members::Alias then [[member.new_name, member.kind]]
|
|
989
|
+
else []
|
|
990
|
+
end
|
|
991
|
+
end
|
|
992
|
+
|
|
993
|
+
def method_definition_keys(member)
|
|
994
|
+
return [] if member.respond_to?(:overloading?) && member.overloading?
|
|
995
|
+
|
|
996
|
+
case member.kind
|
|
997
|
+
when :instance then [[member.name, :instance]]
|
|
998
|
+
when :singleton then [[member.name, :singleton]]
|
|
999
|
+
else [[member.name, :instance], [member.name, :singleton]] # `def self?.x` defines both sides
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
# A class / module declaration header, as the pre-filter in {.signature_member_owners} reads it out of
|
|
1004
|
+
# synthesized RBS text.
|
|
1005
|
+
INLINE_DECLARATION_NAME = /^[ \t]*(?:class|module)[ \t]+([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)/
|
|
1006
|
+
private_constant :INLINE_DECLARATION_NAME
|
|
1007
|
+
|
|
1008
|
+
# Every `::`-separated segment of every class / module name the inline sources declare, read off the
|
|
1009
|
+
# synthesized TEXT rather than a parse: the set only has to be a SUPERSET (it feeds the pre-filter),
|
|
1010
|
+
# and a regexp over the handful of annotated files is far cheaper than parsing them a second time.
|
|
1011
|
+
# Entries {.add_virtual_rbs} would skip are skipped here too — an empty or invalid-UTF-8 contribution
|
|
1012
|
+
# declares nothing, and `String#scan` raises `ArgumentError` on invalid bytes rather than returning
|
|
1013
|
+
# no match.
|
|
1014
|
+
def inline_declared_name_segments(virtual_rbs)
|
|
1015
|
+
virtual_rbs.each_with_object(Set.new) do |(_name, content), acc|
|
|
1016
|
+
content = content.to_s
|
|
1017
|
+
next if content.empty? || invalid_encoding?(content)
|
|
1018
|
+
|
|
1019
|
+
content.scan(INLINE_DECLARATION_NAME) { |(declared)| acc.merge(declared.split("::")) }
|
|
1020
|
+
end
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
# Parses one project signature file, or nil when it is unreadable, not valid UTF-8, unparseable, or
|
|
1024
|
+
# mentions none of `names` (so nothing in it can collide with the inline sources).
|
|
1025
|
+
def parse_signature_file_if_mentions(file, names)
|
|
1026
|
+
content = File.read(file, encoding: "UTF-8")
|
|
1027
|
+
return nil if invalid_encoding?(content)
|
|
1028
|
+
return nil if names.none? { |name| content.include?(name) }
|
|
1029
|
+
|
|
1030
|
+
parse_signature_source(file, content)
|
|
1031
|
+
rescue Errno::ENOENT, Errno::EISDIR, Errno::EACCES
|
|
1032
|
+
nil
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
# The declarations of already-read RBS text, or nil when it does not parse. The twin of
|
|
1036
|
+
# {.parse_signature_file} for content the caller already holds (a synthesized virtual source, or a
|
|
1037
|
+
# signature file the pre-filter has just read).
|
|
1038
|
+
def parse_signature_source(name, content)
|
|
1039
|
+
_buffer, _directives, decls = ::RBS::Parser.parse_signature(
|
|
1040
|
+
::RBS::Buffer.new(name: name.to_s, content: content)
|
|
1041
|
+
)
|
|
1042
|
+
decls
|
|
1043
|
+
rescue ::RBS::BaseError
|
|
1044
|
+
nil
|
|
1045
|
+
end
|
|
1046
|
+
|
|
735
1047
|
# Per-gem `data/vendored_gem_sigs/<gem>/` directories that ship with Rigor. Each subdirectory is one
|
|
736
1048
|
# gem's RBS surface (the `<gem>.rbs` file is the typical content; `LICENSE.upstream` records
|
|
737
1049
|
# provenance). Coverage is deliberately scoped to the native-extension and "everywhere in Rails" gems
|
|
@@ -758,6 +1070,51 @@ module Rigor
|
|
|
758
1070
|
[Pathname(CORE_OVERLAY_SIGS_ROOT)]
|
|
759
1071
|
end
|
|
760
1072
|
|
|
1073
|
+
# Rigor's capability-role catalog (`data/capability_roles/`) — the structural interfaces ADR-1 and
|
|
1074
|
+
# `docs/type-specification/structural-interfaces-and-object-shapes.md` name as shipped, so a
|
|
1075
|
+
# `%a{rigor:v1:conforms-to _ClosableStream}` written by following the specification resolves in a
|
|
1076
|
+
# default run instead of failing soft to an unresolved-interface row. Public for the same reason
|
|
1077
|
+
# {.core_overlay_sig_paths} is: the cache descriptor digests these files into the env-blob key.
|
|
1078
|
+
CAPABILITY_ROLES_SIGS_ROOT = File.expand_path(
|
|
1079
|
+
"../../../data/capability_roles",
|
|
1080
|
+
__dir__
|
|
1081
|
+
).freeze
|
|
1082
|
+
|
|
1083
|
+
def capability_role_sig_paths
|
|
1084
|
+
return [] unless File.directory?(CAPABILITY_ROLES_SIGS_ROOT)
|
|
1085
|
+
|
|
1086
|
+
[Pathname(CAPABILITY_ROLES_SIGS_ROOT)]
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1089
|
+
# Adds the catalog to an env that already holds the project's own signatures, one INTERFACE at a
|
|
1090
|
+
# time, skipping any name the env already declares. Granularity is the declaration rather than the
|
|
1091
|
+
# file (the #610 stand-down's unit) because this one file carries five unrelated roles: a project
|
|
1092
|
+
# declaring `_Closable` must not also lose `_ClosableStream`.
|
|
1093
|
+
def add_capability_role_signatures(env)
|
|
1094
|
+
declared = declared_interface_names(env)
|
|
1095
|
+
capability_role_sig_paths.each do |dir|
|
|
1096
|
+
dir.children.sort.each do |file|
|
|
1097
|
+
next unless file.file? && file.extname == ".rbs"
|
|
1098
|
+
|
|
1099
|
+
parsed = parse_signature_file(file.to_s)
|
|
1100
|
+
next if parsed.nil?
|
|
1101
|
+
|
|
1102
|
+
buffer, directives, decls = parsed
|
|
1103
|
+
standing = decls.reject { |decl| declared.include?(decl.name.absolute!) }
|
|
1104
|
+
next if standing.empty?
|
|
1105
|
+
|
|
1106
|
+
add_parsed_decls(env, buffer, directives, standing)
|
|
1107
|
+
end
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
# @param env — the environment the project's signatures are already in.
|
|
1112
|
+
def declared_interface_names(env)
|
|
1113
|
+
return Set.new unless env.respond_to?(:interface_decls)
|
|
1114
|
+
|
|
1115
|
+
env.interface_decls.keys.to_set
|
|
1116
|
+
end
|
|
1117
|
+
|
|
761
1118
|
# Bundled signature sources that SUPPLEMENT a stdlib library's own declarations rather than stand
|
|
762
1119
|
# alone: each re-opens a class the named `RBS::EnvironmentLoader` library declares with a mixin
|
|
763
1120
|
# (`CGI::QueryExtension`), a superclass (`Prism::Result`), or an `| ...` overload continuation
|
|
@@ -794,7 +1151,7 @@ module Rigor
|
|
|
794
1151
|
# that Rigor's arithmetic-chain widening produces). The overlay is added per-file, not
|
|
795
1152
|
# per-directory, because the `LIBRARY_SUPPLEMENT_CORE_OVERLAYS` files must be gated individually.
|
|
796
1153
|
#
|
|
797
|
-
# @param loaded_library_names
|
|
1154
|
+
# @param loaded_library_names — libraries that actually resolved on this loader.
|
|
798
1155
|
def add_bundled_signatures(rbs_loader, loaded_library_names)
|
|
799
1156
|
vendored_gem_sig_paths.each do |path|
|
|
800
1157
|
next unless path.directory?
|
|
@@ -814,10 +1171,10 @@ module Rigor
|
|
|
814
1171
|
end
|
|
815
1172
|
end
|
|
816
1173
|
|
|
817
|
-
# @param supplements
|
|
818
|
-
# @param path
|
|
819
|
-
# @param loaded_library_names
|
|
820
|
-
# @return
|
|
1174
|
+
# @param supplements — basename → gating library map.
|
|
1175
|
+
# @param path — the vendored directory or overlay file to test.
|
|
1176
|
+
# @param loaded_library_names — libraries that actually resolved on this loader.
|
|
1177
|
+
# @return true when `path` carries no library dependency, or its library loaded.
|
|
821
1178
|
def supplement_dependency_loaded?(supplements, path, loaded_library_names)
|
|
822
1179
|
library = supplements[path.basename.to_s]
|
|
823
1180
|
library.nil? || loaded_library_names.include?(library)
|
|
@@ -833,9 +1190,9 @@ module Rigor
|
|
|
833
1190
|
__dir__
|
|
834
1191
|
).freeze
|
|
835
1192
|
|
|
836
|
-
# @param gem_names
|
|
1193
|
+
# @param gem_names — overlay-eligible Gemfile.lock gem names (the caller filters
|
|
837
1194
|
# to the `:missing`-coverage, no-conflicting-plugin set).
|
|
838
|
-
# @return
|
|
1195
|
+
# @return the bundled overlay directory for each gem that ships one; empty when
|
|
839
1196
|
# none match or the overlay root is absent.
|
|
840
1197
|
def gem_overlay_sig_paths(gem_names)
|
|
841
1198
|
return [] unless File.directory?(GEM_OVERLAY_SIGS_ROOT)
|
|
@@ -846,9 +1203,9 @@ module Rigor
|
|
|
846
1203
|
end
|
|
847
1204
|
end
|
|
848
1205
|
|
|
849
|
-
# @param path
|
|
1206
|
+
# @param path — typically an entry from an {RbsLoader} instance's
|
|
850
1207
|
# {#signature_paths}.
|
|
851
|
-
# @return
|
|
1208
|
+
# @return whether `path` sits under the bundled gem-overlay root — what
|
|
852
1209
|
# `CheckRules::GEM_OVERLAY_OPEN_RECEIVERS`'s gate consults so that a class name's membership in
|
|
853
1210
|
# that list alone never grants the open-receiver exemption; the overlay directory that made the
|
|
854
1211
|
# entry true must have actually loaded THIS run too (issue #632, tracked further by #660).
|
|
@@ -886,7 +1243,7 @@ module Rigor
|
|
|
886
1243
|
# answer to the very same question, on the class that already owns the overlay's layout
|
|
887
1244
|
# ({GEM_OVERLAY_SIGS_ROOT}, {.gem_overlay_sig_paths}).
|
|
888
1245
|
#
|
|
889
|
-
# @param signature_paths
|
|
1246
|
+
# @param signature_paths — typically an {RbsLoader} instance's
|
|
890
1247
|
# {#signature_paths}. Takes the whole list rather than one entry so the twin's file set resolves
|
|
891
1248
|
# once per question — `CheckRules` asks it per `ActiveSupport::Duration` receiver.
|
|
892
1249
|
def gem_overlay_twin_signatures_loaded?(signature_paths)
|
|
@@ -901,7 +1258,7 @@ module Rigor
|
|
|
901
1258
|
# id mapping and passes the id; this resolves the id to the engine's own bundled `sig/` and answers
|
|
902
1259
|
# the filesystem question.
|
|
903
1260
|
#
|
|
904
|
-
# @param plugin_id
|
|
1261
|
+
# @param plugin_id — a manifest id, e.g. `"activesupport-core-ext"`.
|
|
905
1262
|
def bundled_overlay_twin_signatures_loaded?(plugin_id, signature_paths)
|
|
906
1263
|
return false if signature_paths.nil? || signature_paths.empty?
|
|
907
1264
|
|
|
@@ -969,23 +1326,27 @@ module Rigor
|
|
|
969
1326
|
end
|
|
970
1327
|
end
|
|
971
1328
|
|
|
972
|
-
attr_reader :libraries, :signature_paths, :cache_store, :virtual_rbs
|
|
1329
|
+
attr_reader :libraries, :signature_paths, :cache_store, :virtual_rbs, :deferred_signature_paths
|
|
973
1330
|
|
|
974
|
-
# @param libraries
|
|
1331
|
+
# @param libraries — stdlib library names to load on top of core (e.g.,
|
|
975
1332
|
# `["pathname", "json"]`). Empty by default. Each entry MUST correspond to a directory under the
|
|
976
1333
|
# `rbs` gem's `stdlib/` tree; unknown names are silently dropped on environment build (the underlying
|
|
977
1334
|
# `RBS::EnvironmentLoader` raises and we fail-soft).
|
|
978
|
-
# @param signature_paths
|
|
1335
|
+
# @param signature_paths — additional directories of `.rbs` files to load
|
|
979
1336
|
# (typically the project's `sig/` tree). Non-existent or non-directory paths are filtered out at
|
|
980
1337
|
# build time so the loader stays robust to fixtures and bare repositories.
|
|
981
|
-
# @param cache_store
|
|
1338
|
+
# @param cache_store — the persistent cache the loader threads through to
|
|
982
1339
|
# `RbsEnvironment`, `RbsKnownClassNames`, `RbsConstantTable`, `RbsClassAncestorTable`, and
|
|
983
1340
|
# `RbsClassTypeParamNames` producers. Pass `nil` (the default) to skip caching; the runner threads
|
|
984
1341
|
# its own Store through here when enabled.
|
|
985
|
-
# @param virtual_rbs
|
|
1342
|
+
# @param virtual_rbs — >] ADR-32 WD4 — `[virtual_filename, rbs_source]` pairs
|
|
986
1343
|
# synthesised from project source by a plugin's `Manifest#source_rbs_synthesizer`. Merged into the
|
|
987
1344
|
# env after `signature_paths:` and the vendored stubs. Pass `[]` (the default) when no
|
|
988
1345
|
# synthesizer-emitting plugin is loaded.
|
|
1346
|
+
# @param deferred_signature_paths — the subset of `signature_paths:` a bundled plugin
|
|
1347
|
+
# contributed (issue #610): loaded last and allowed to stand down against a colliding generic
|
|
1348
|
+
# arity. Read back by the env-cache producer, which rebuilds the env from the loader's readers on
|
|
1349
|
+
# a miss — a list it cannot read is a stand-down that runs only on a `--no-cache` build.
|
|
989
1350
|
def initialize(libraries: [], signature_paths: [], cache_store: nil, virtual_rbs: [],
|
|
990
1351
|
deferred_signature_paths: [])
|
|
991
1352
|
@libraries = libraries.map(&:to_s).freeze
|
|
@@ -1000,8 +1361,10 @@ module Rigor
|
|
|
1000
1361
|
# `.freeze`d (per ADR-15 reflection-facade contract) without losing the lazy-memo behaviour. Slot
|
|
1001
1362
|
# names currently consulted: `:env`, `:env_loaded`, `:env_build_warned`, `:definition_build_warned`,
|
|
1002
1363
|
# `:definition_build_details`, `:definition_build_reported`, `:definition_build_failures`,
|
|
1003
|
-
# `:
|
|
1004
|
-
# `:
|
|
1364
|
+
# `:definition_build_deferred_count`, `:definition_build_deferred_first`,
|
|
1365
|
+
# `:definition_build_summary_warned`, `:inline_member_standdowns`, `:internal_demand`,
|
|
1366
|
+
# `:internal_demand_status`, `:builder`, `:reflection`,
|
|
1367
|
+
# `:instance_definitions_table`, `:singleton_definitions_table`.
|
|
1005
1368
|
# Constructed via `Hash.new` (NOT a `{ ... }` literal) so Rigor's `HashShape` narrowing doesn't
|
|
1006
1369
|
# infer a fixed key set from the initial state and fold post-initial slot reads (e.g.
|
|
1007
1370
|
# `@state[:env_loaded]`) to a constant `nil`.
|
|
@@ -1029,15 +1392,32 @@ module Rigor
|
|
|
1029
1392
|
# the stderr banner) reads it, and a cache HIT reaches it too — the env was built with the file already
|
|
1030
1393
|
# quarantined, so the condition is invisible in the cached env itself.
|
|
1031
1394
|
#
|
|
1032
|
-
# @return
|
|
1395
|
+
# @return empty when every `signature_paths:` file parses.
|
|
1033
1396
|
def quarantined_signatures
|
|
1034
|
-
@state[:quarantined] ||=
|
|
1397
|
+
@state[:quarantined] ||= begin
|
|
1398
|
+
parse_quarantined = self.class.quarantined_project_signatures(@signature_paths)
|
|
1399
|
+
collision_quarantined = self.class.collision_quarantined_project_signatures(
|
|
1400
|
+
@state[:env], @signature_paths
|
|
1401
|
+
)
|
|
1402
|
+
parse_paths = parse_quarantined.to_set { |path, _note| path }
|
|
1403
|
+
# Issue #610 — a deferred file that STOOD DOWN is absent from the env by design, which is exactly
|
|
1404
|
+
# what the collision derivation reads as "quarantined". Derived from the same env, so a cache HIT
|
|
1405
|
+
# excludes it too; {#signature_standdowns} is where it is reported instead.
|
|
1406
|
+
stood_down = self.class.deferred_standdowns(@state[:env], @deferred_signature_paths).to_set(&:first)
|
|
1407
|
+
(
|
|
1408
|
+
parse_quarantined +
|
|
1409
|
+
collision_quarantined.reject do |entry|
|
|
1410
|
+
parse_paths.include?(entry[0]) || stood_down.include?(entry[0])
|
|
1411
|
+
end
|
|
1412
|
+
).freeze
|
|
1413
|
+
end
|
|
1035
1414
|
end
|
|
1036
1415
|
|
|
1037
1416
|
# Issue #610 — plugin-contributed signature files that stood down against a colliding generic arity,
|
|
1038
|
-
# as `[absolute_path, class_name, existing_arity, incoming_arity]`. Empty whenever no
|
|
1039
|
-
# collides, which is every project that has not run `rbs collection install` for a gem a
|
|
1040
|
-
# plugin also declares.
|
|
1417
|
+
# as `[absolute_path, class_name, existing_arity, incoming_arity, existing_file]`. Empty whenever no
|
|
1418
|
+
# plugin sig collides, which is every project that has not run `rbs collection install` for a gem a
|
|
1419
|
+
# bundled plugin also declares. Reported as `rbs.coverage.plugin-signature-stood-down` (`:info`); the
|
|
1420
|
+
# same file MUST NOT also appear in {#quarantined_signatures}.
|
|
1041
1421
|
def signature_standdowns
|
|
1042
1422
|
@state[:signature_standdowns] ||= self.class.deferred_standdowns(env, @deferred_signature_paths).freeze
|
|
1043
1423
|
end
|
|
@@ -1083,14 +1463,15 @@ module Rigor
|
|
|
1083
1463
|
end
|
|
1084
1464
|
|
|
1085
1465
|
# The total RBS-environment build failure captured this run, or nil when the env built. Unlike
|
|
1086
|
-
# {#quarantined_signatures} — which the env survives, one file lighter, and which is re-derived
|
|
1087
|
-
#
|
|
1088
|
-
#
|
|
1089
|
-
#
|
|
1090
|
-
#
|
|
1091
|
-
# re-
|
|
1466
|
+
# {#quarantined_signatures} — which the env survives, one file lighter, and which is re-derived so a
|
|
1467
|
+
# cache HIT reports it too — a total failure collapses the WHOLE env to nil. Project-vs-bundled
|
|
1468
|
+
# `RBS::DuplicatedDeclarationError`s are quarantined per-file since #777; this slot remains for
|
|
1469
|
+
# unrecoverable build errors (e.g. bundled-vs-bundled collisions, or hosts without a transactional
|
|
1470
|
+
# `sources`/`unload` API). A failed build produces no cached success to hide behind (nothing is
|
|
1471
|
+
# persisted, so every run re-attempts and re-raises), so this is captured directly in {#env}'s rescue
|
|
1472
|
+
# rather than re-derived. Forcing `env` (any query does) populates it.
|
|
1092
1473
|
#
|
|
1093
|
-
# @return
|
|
1474
|
+
# @return `[error_class_name, first_error_line,
|
|
1094
1475
|
# conflicting_buffer_names]`, or nil when the environment built successfully.
|
|
1095
1476
|
def env_build_failure
|
|
1096
1477
|
env unless @state[:env_loaded]
|
|
@@ -1114,7 +1495,7 @@ module Rigor
|
|
|
1114
1495
|
# definition-build failure leaves no trace in the env at all — the env is fine; it is the BUILD over it
|
|
1115
1496
|
# that raised — so the rescue is the only place that ever knows.
|
|
1116
1497
|
#
|
|
1117
|
-
# @return
|
|
1498
|
+
# @return `[class_name, error_class_name,
|
|
1118
1499
|
# first_error_line, conflicting_buffer_names]`, one per class, in first-failure order. Empty for a
|
|
1119
1500
|
# healthy sig set, which is the common case.
|
|
1120
1501
|
def definition_build_failures
|
|
@@ -1128,7 +1509,7 @@ module Rigor
|
|
|
1128
1509
|
# the {#quarantined_signatures} trick — so a cache HIT, which never runs the build, reports the same
|
|
1129
1510
|
# condition: the marshalled env simply lacks the dropped buffers.
|
|
1130
1511
|
#
|
|
1131
|
-
# @return
|
|
1512
|
+
# @return virtual buffer names (source-file paths) whose contribution was dropped.
|
|
1132
1513
|
def virtual_rbs_collision_quarantined
|
|
1133
1514
|
@state[:virtual_rbs_collisions] ||= begin
|
|
1134
1515
|
built = @state[:env]
|
|
@@ -1143,6 +1524,25 @@ module Rigor
|
|
|
1143
1524
|
end
|
|
1144
1525
|
end
|
|
1145
1526
|
|
|
1527
|
+
# Issue #824 / ADR-32 WD13 — the inline (rbs-inline) members that stood down because the project's own
|
|
1528
|
+
# `signature_paths:` already declare the same `(class, method, kind)`. The `.rbs` wins per member; the
|
|
1529
|
+
# rest of the file's annotations still bind, and the class keeps its method surface instead of failing
|
|
1530
|
+
# its definition build.
|
|
1531
|
+
#
|
|
1532
|
+
# The sibling of {#virtual_rbs_collision_quarantined} one granularity finer: that one names a whole
|
|
1533
|
+
# inline FILE dropped for a class-level duplicate, this one names a single member. Both are derived
|
|
1534
|
+
# rather than recorded, so a cache HIT — where no build ran — reports the same condition; here the
|
|
1535
|
+
# derivation reads the loader's inputs rather than the built env, because a stripped member leaves no
|
|
1536
|
+
# trace in the env at all.
|
|
1537
|
+
#
|
|
1538
|
+
# @return `[class_name, method_name, kind, signature_path,
|
|
1539
|
+
# virtual_buffer_name]` records, sorted. Empty (and free) whenever the project contributes no inline
|
|
1540
|
+
# RBS or none of it overlaps a signature file.
|
|
1541
|
+
def inline_member_standdowns
|
|
1542
|
+
@state[:inline_member_standdowns] ||=
|
|
1543
|
+
self.class.inline_member_standdowns_for(signature_paths: @signature_paths, virtual_rbs: @virtual_rbs)
|
|
1544
|
+
end
|
|
1545
|
+
|
|
1146
1546
|
# The referenced-but-undeclared types {.stub_missing_referenced_types} stubbed so the project classes
|
|
1147
1547
|
# that mention them could build (e.g. an unavailable `DRb::DRbServer`, or a stale
|
|
1148
1548
|
# `Textbringer::EditorError`). Recovered off the built env like {#synthesized_namespaces}, so it
|
|
@@ -1306,7 +1706,7 @@ module Rigor
|
|
|
1306
1706
|
{}.freeze
|
|
1307
1707
|
end
|
|
1308
1708
|
|
|
1309
|
-
# @return
|
|
1709
|
+
# @return the resolved instance definition for `class_name`, or nil when the
|
|
1310
1710
|
# class is unknown or its definition cannot be built (RBS may raise on broken hierarchies; we
|
|
1311
1711
|
# fail-soft and return nil so the caller can fall back).
|
|
1312
1712
|
#
|
|
@@ -1332,7 +1732,6 @@ module Rigor
|
|
|
1332
1732
|
definition
|
|
1333
1733
|
end
|
|
1334
1734
|
|
|
1335
|
-
# @return [RBS::Definition::Method, nil]
|
|
1336
1735
|
def instance_method(class_name:, method_name:)
|
|
1337
1736
|
definition = instance_definition(class_name)
|
|
1338
1737
|
return nil unless definition
|
|
@@ -1340,7 +1739,7 @@ module Rigor
|
|
|
1340
1739
|
definition.methods[method_name.to_sym]
|
|
1341
1740
|
end
|
|
1342
1741
|
|
|
1343
|
-
# @return
|
|
1742
|
+
# @return every instance-method name on `class_name` — own, inherited, and
|
|
1344
1743
|
# included — as resolved by `RBS::DefinitionBuilder`. Returns `nil` (NOT `[]`) when the class
|
|
1345
1744
|
# definition cannot be built so callers can tell "no methods" apart from "unknown class". Used by the
|
|
1346
1745
|
# `rigor:v1:conforms-to` presence check ({Rigor::RbsExtended::ConformanceChecker}).
|
|
@@ -1351,7 +1750,7 @@ module Rigor
|
|
|
1351
1750
|
definition.methods.keys
|
|
1352
1751
|
end
|
|
1353
1752
|
|
|
1354
|
-
# @return
|
|
1753
|
+
# @return the built definition for the RBS interface `interface_name`
|
|
1355
1754
|
# (`_RewindableStream`), whose `.methods` are the required members (including interface-ancestor
|
|
1356
1755
|
# members). Returns `nil` when the name does not resolve to a loaded interface (a typo, or the
|
|
1357
1756
|
# defining library / sig set is not on the load path). Fail-soft on RBS build errors.
|
|
@@ -1366,15 +1765,15 @@ module Rigor
|
|
|
1366
1765
|
nil
|
|
1367
1766
|
end
|
|
1368
1767
|
|
|
1369
|
-
# @return
|
|
1768
|
+
# @return every method name required by the RBS interface `interface_name`, or nil
|
|
1370
1769
|
# when it does not resolve. Thin accessor over {#interface_definition} for the presence check.
|
|
1371
1770
|
def interface_method_names(interface_name)
|
|
1372
1771
|
interface_definition(interface_name)&.methods&.keys
|
|
1373
1772
|
end
|
|
1374
1773
|
|
|
1375
|
-
# @param rbs_alias
|
|
1774
|
+
# @param rbs_alias — a type-alias reference (`string`, `int`, `range[int?]`, …)
|
|
1376
1775
|
# appearing in a method signature.
|
|
1377
|
-
# @return
|
|
1776
|
+
# @return the alias's aliased type one level out, with type arguments substituted
|
|
1378
1777
|
# for a generic alias (`string` → `::String | ::_ToStr`; `range[int?]` → `::Range[int?] |
|
|
1379
1778
|
# ::_Range[int?]`), or nil for an unresolved name. Lets a caller see through the alias that
|
|
1380
1779
|
# {Inference::RbsTypeTranslator} otherwise degrades to `untyped`, which is why an interface/alias
|
|
@@ -1399,7 +1798,7 @@ module Rigor
|
|
|
1399
1798
|
nil
|
|
1400
1799
|
end
|
|
1401
1800
|
|
|
1402
|
-
# @return
|
|
1801
|
+
# @return the resolved singleton (class object) definition for `class_name`. The
|
|
1403
1802
|
# methods on this definition are the *class methods* of `class_name`, including those inherited from
|
|
1404
1803
|
# `Class` and `Module` for class types. Returns nil for unknown names and on RBS build errors
|
|
1405
1804
|
# (fail-soft).
|
|
@@ -1420,7 +1819,37 @@ module Rigor
|
|
|
1420
1819
|
definition
|
|
1421
1820
|
end
|
|
1422
1821
|
|
|
1423
|
-
#
|
|
1822
|
+
# Issue #915 — the MODULE names in `class_name`'s singleton ancestry, as RBS declares it. A
|
|
1823
|
+
# `class C; extend M; end` written in a project's own `sig/` puts `M` there and nothing else in the
|
|
1824
|
+
# environment surface exposes it: {#ancestor_names_for} answers the instance side only.
|
|
1825
|
+
#
|
|
1826
|
+
# Modules only, deliberately. `RBS::DefinitionBuilder#build_singleton` mixes the extended modules in
|
|
1827
|
+
# with the singleton's class chain (`Class`, `Module`, `Object`, `BasicObject`), and the classes there
|
|
1828
|
+
# are already what the `Class` approximation in `Inference::Narrowing` reasons through. The modules
|
|
1829
|
+
# that survive the filter — the declared `extend` targets, plus `Kernel` through `Object` — are each a
|
|
1830
|
+
# true answer to `C.is_a?(M)`.
|
|
1831
|
+
#
|
|
1832
|
+
# Marked as an internal demand for the same reason {#ancestor_names_for} is (issue #696): asking
|
|
1833
|
+
# whether a module sits in an ancestry is not asking whether the class's method surface resolves, and
|
|
1834
|
+
# letting it report would make the reported class list depend on the cache state.
|
|
1835
|
+
#
|
|
1836
|
+
# @return `::`-stripped module names, or `[]` for an unknown class and on any RBS build error.
|
|
1837
|
+
def singleton_extended_module_names(class_name)
|
|
1838
|
+
key = class_name.to_s.delete_prefix("::")
|
|
1839
|
+
during_internal_demand do
|
|
1840
|
+
definition = singleton_definition(key)
|
|
1841
|
+
next [].freeze if definition.nil?
|
|
1842
|
+
|
|
1843
|
+
definition.ancestors.ancestors.filter_map do |ancestor|
|
|
1844
|
+
name = ancestor.name.to_s.delete_prefix("::")
|
|
1845
|
+
name if ancestor.is_a?(::RBS::Definition::Ancestor::Instance) && rbs_module?(name)
|
|
1846
|
+
end.uniq.freeze
|
|
1847
|
+
end
|
|
1848
|
+
rescue ::RBS::BaseError, StandardError
|
|
1849
|
+
[].freeze
|
|
1850
|
+
end
|
|
1851
|
+
|
|
1852
|
+
# @return the class method on `class_name`. For example,
|
|
1424
1853
|
# `singleton_method(class_name: "Integer", method_name: :sqrt)` returns the definition for
|
|
1425
1854
|
# `Integer.sqrt`, while `singleton_method(class_name: "Foo", method_name: :new)` returns Class#new
|
|
1426
1855
|
# for any class type.
|
|
@@ -1444,18 +1873,29 @@ module Rigor
|
|
|
1444
1873
|
# process) through {Cache::RbsClassTypeParamNames.fetch} and answers point lookups from it. Cold runs
|
|
1445
1874
|
# build the table once and persist it; warm runs (and a separate loader sharing the same Store) skip
|
|
1446
1875
|
# the env walk entirely.
|
|
1876
|
+
#
|
|
1877
|
+
# #775 — memoised per class name and answered as a frozen list. `RBS::Definition#type_params` is not
|
|
1878
|
+
# an accessor: it re-derives the names through the entry's type-param declarations (a substitution
|
|
1879
|
+
# per call), and generic dispatch asks for the receiver class's names on every call (~110k times on
|
|
1880
|
+
# the lib self-check, the largest remaining rbs-side allocation). The env is immutable for the
|
|
1881
|
+
# loader's lifetime, so the answer never changes; every consumer only reads it.
|
|
1447
1882
|
def class_type_param_names(class_name)
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
end
|
|
1452
|
-
|
|
1453
|
-
definition = instance_definition(class_name)
|
|
1454
|
-
return [] unless definition
|
|
1883
|
+
memo = (@state[:type_param_names] ||= {})
|
|
1884
|
+
key = class_name.to_s
|
|
1885
|
+
return memo[key] if memo.key?(key)
|
|
1455
1886
|
|
|
1456
|
-
|
|
1887
|
+
memo[key] =
|
|
1888
|
+
if cache_store
|
|
1889
|
+
type_param_names_table.fetch(key.delete_prefix("::"), NO_TYPE_PARAMS).freeze
|
|
1890
|
+
else
|
|
1891
|
+
definition = instance_definition(class_name)
|
|
1892
|
+
definition ? definition.type_params.freeze : NO_TYPE_PARAMS
|
|
1893
|
+
end
|
|
1457
1894
|
end
|
|
1458
1895
|
|
|
1896
|
+
NO_TYPE_PARAMS = [].freeze
|
|
1897
|
+
private_constant :NO_TYPE_PARAMS
|
|
1898
|
+
|
|
1459
1899
|
def class_ordering(lhs, rhs)
|
|
1460
1900
|
@hierarchy.class_ordering(lhs, rhs)
|
|
1461
1901
|
end
|
|
@@ -1486,7 +1926,7 @@ module Rigor
|
|
|
1486
1926
|
# way, and both yield `[]` for an unknown or unbuildable class. Pinned by spec across all three cache
|
|
1487
1927
|
# states.
|
|
1488
1928
|
#
|
|
1489
|
-
# @return
|
|
1929
|
+
# @return `::`-stripped ancestor names, or `[]` for an unknown or unbuildable class.
|
|
1490
1930
|
def ancestor_names_for(class_name)
|
|
1491
1931
|
key = class_name.to_s.delete_prefix("::")
|
|
1492
1932
|
during_internal_demand do
|
|
@@ -1501,7 +1941,7 @@ module Rigor
|
|
|
1501
1941
|
[].freeze
|
|
1502
1942
|
end
|
|
1503
1943
|
|
|
1504
|
-
# @return
|
|
1944
|
+
# @return every RBS-declared constant name (top-level prefixed, e.g., `"::Math::PI"`)
|
|
1505
1945
|
# currently loaded into the environment. Used by the cache producer that materialises the
|
|
1506
1946
|
# constant-type table; ordinary callers should keep using {#constant_type} for point lookups.
|
|
1507
1947
|
def constant_names
|
|
@@ -1736,11 +2176,10 @@ module Rigor
|
|
|
1736
2176
|
end
|
|
1737
2177
|
|
|
1738
2178
|
# The RBS environment for this loader. Memoised both on success AND on failure: when the env build
|
|
1739
|
-
# raises
|
|
1740
|
-
#
|
|
1741
|
-
#
|
|
1742
|
-
#
|
|
1743
|
-
# `warn_about_env_build_failure_once` so the broken `signature_paths:` entry is identifiable.
|
|
2179
|
+
# raises an *unrecoverable* `RBS::BaseError` (project-vs-bundled duplicates are quarantined per-file
|
|
2180
|
+
# since #777), retrying on every subsequent `env` call would re-parse and re-resolve the whole sig set
|
|
2181
|
+
# per AST node touched during analysis, multiplying per-file analysis cost by ~100x. Failures
|
|
2182
|
+
# short-circuit to `nil` here and are surfaced to the user via `warn_about_env_build_failure_once`.
|
|
1744
2183
|
def env
|
|
1745
2184
|
return @state[:env] if @state[:env_loaded]
|
|
1746
2185
|
|
|
@@ -1778,10 +2217,11 @@ module Rigor
|
|
|
1778
2217
|
lines = listed.map { |_path, first_line| " - #{first_line}" }
|
|
1779
2218
|
lines << " … and #{more} more" if more.positive?
|
|
1780
2219
|
warn(
|
|
1781
|
-
"rigor: skipped #{quarantined.size}
|
|
1782
|
-
"They were QUARANTINED so the rest of your RBS env
|
|
1783
|
-
"declare are absent — calls into them read
|
|
1784
|
-
"are reduced. Fix the parse error(s)
|
|
2220
|
+
"rigor: skipped #{quarantined.size} RBS file(s) under `signature_paths:` (unparseable or\n " \
|
|
2221
|
+
"duplicated against bundled RBS). They were QUARANTINED so the rest of your RBS env\n " \
|
|
2222
|
+
"still loads, but the types they declare are absent — calls into them read\n " \
|
|
2223
|
+
"`Dynamic[top]`, so coverage and diagnostics are reduced. Fix the parse error(s) or\n " \
|
|
2224
|
+
"remove the conflicting declaration(s) to restore that coverage:\n" \
|
|
1785
2225
|
"#{lines.join("\n")}"
|
|
1786
2226
|
)
|
|
1787
2227
|
end
|
|
@@ -1870,8 +2310,8 @@ module Rigor
|
|
|
1870
2310
|
#
|
|
1871
2311
|
# {#during_internal_demand} (issue #696) — these two walk EVERY known class, so a failure they hit is a
|
|
1872
2312
|
# failure of the sig set, not of anything the run asked about, and it must not reach
|
|
1873
|
-
# {#definition_build_failures}. The stderr
|
|
1874
|
-
#
|
|
2313
|
+
# {#definition_build_failures}. The stderr fallback stays armed, but {#during_internal_demand} folds
|
|
2314
|
+
# every failure found by one outermost walk into one bounded summary (issue #718).
|
|
1875
2315
|
def instance_definitions_table
|
|
1876
2316
|
@state[:instance_definitions_table] ||= during_internal_demand do
|
|
1877
2317
|
build_definitions_table { |name| build_instance_definition(name) }
|
|
@@ -2005,7 +2445,9 @@ module Rigor
|
|
|
2005
2445
|
# review, second pass).
|
|
2006
2446
|
#
|
|
2007
2447
|
# Save-and-restore rather than a bare flag: `#prewarm` wraps a body whose members wrap themselves, and
|
|
2008
|
-
# a nested demand must not un-mark its caller on the way out.
|
|
2448
|
+
# a nested demand must not un-mark its caller on the way out. A successful outermost exit flushes the
|
|
2449
|
+
# deferred stderr summary; an outermost exit that raises keeps the detailed fallback banner armed even
|
|
2450
|
+
# when an earlier internal-demand episode already emitted the loader-instance summary.
|
|
2009
2451
|
#
|
|
2010
2452
|
# Per LOADER, not per thread. Nesting and a raise mid-demand are both handled, and the fork pool forks
|
|
2011
2453
|
# after `#prewarm` returns, so no CLI path shares a loader across concurrent analyses. An in-process
|
|
@@ -2013,10 +2455,29 @@ module Rigor
|
|
|
2013
2455
|
# silence another's reporting; not reachable today, and not worth a thread-local until it is.
|
|
2014
2456
|
def during_internal_demand
|
|
2015
2457
|
previous = @state[:internal_demand]
|
|
2458
|
+
outermost = !previous
|
|
2459
|
+
if outermost
|
|
2460
|
+
@state[:definition_build_deferred_count] = 0
|
|
2461
|
+
@state[:definition_build_deferred_first] = nil
|
|
2462
|
+
end
|
|
2016
2463
|
@state[:internal_demand] = true
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
@state[:
|
|
2464
|
+
# `rescue`/`else` keeps the normal block result precise for the analyzer; the pending marker covers
|
|
2465
|
+
# non-local exits (e.g. `throw`) that unwind through `ensure` without entering either branch.
|
|
2466
|
+
@state[:internal_demand_status] = :pending if outermost
|
|
2467
|
+
begin
|
|
2468
|
+
result = yield
|
|
2469
|
+
@state[:internal_demand_status] = :completed if outermost
|
|
2470
|
+
rescue StandardError, ScriptError => e
|
|
2471
|
+
@state[:internal_demand_status] = :aborted if outermost
|
|
2472
|
+
warn_about_aborted_definition_build_failures if outermost
|
|
2473
|
+
raise e
|
|
2474
|
+
else
|
|
2475
|
+
warn_about_deferred_definition_build_failures if outermost
|
|
2476
|
+
result
|
|
2477
|
+
ensure
|
|
2478
|
+
@state[:internal_demand] = previous
|
|
2479
|
+
warn_about_aborted_definition_build_failures if outermost && @state[:internal_demand_status] == :pending
|
|
2480
|
+
end
|
|
2020
2481
|
end
|
|
2021
2482
|
|
|
2022
2483
|
# The third twin of {#warn_about_quarantined_signatures} / {#warn_about_virtual_rbs_collisions}: name,
|
|
@@ -2029,21 +2490,33 @@ module Rigor
|
|
|
2029
2490
|
# {#env}, because the whole env is built eagerly and every quarantine/collision is already known by
|
|
2030
2491
|
# then. Definition builds are LAZY (ADR-54 WD1 — built on demand per class the FIRST time a caller asks,
|
|
2031
2492
|
# long after {#env} has already run), so there is no later central checkpoint to fire from before the
|
|
2032
|
-
# affected classes even exist.
|
|
2033
|
-
#
|
|
2034
|
-
#
|
|
2035
|
-
#
|
|
2036
|
-
#
|
|
2037
|
-
#
|
|
2038
|
-
# `@state
|
|
2039
|
-
#
|
|
2040
|
-
#
|
|
2493
|
+
# affected classes even exist. An ordinary demand therefore warns inline at the rescue site; Rigor's
|
|
2494
|
+
# own internal demand defers until its outermost boundary and collapses the whole walk to one summary
|
|
2495
|
+
# (issue #718). Both routes share `@state[:definition_build_warned]` (keyed by normalized class name),
|
|
2496
|
+
# so the instance and singleton sides — and any re-entry once the per-loader-instance definition caches memoize
|
|
2497
|
+
# the failure — count or warn at most once per class name, cache-hit runs included.
|
|
2498
|
+
#
|
|
2499
|
+
# `@state` is per LOADER INSTANCE, not process-global. A fork-pool prewarm finishes before the fork, so
|
|
2500
|
+
# that loader instance emits its summary once in the parent and its dedupe state is inherited by the
|
|
2501
|
+
# workers. A separate loader instance (for example, a parameter-inference pre-pass) has its own summary
|
|
2502
|
+
# budget. A failure first reached by an ordinary demand can still warn once in each worker that reaches
|
|
2503
|
+
# it; deduplicating that across processes is out of scope here — see [#295](https://github.com/rigortype/rigor/issues/295).
|
|
2041
2504
|
def warn_about_definition_build_failure(class_name, error)
|
|
2042
2505
|
warned = (@state[:definition_build_warned] ||= {})
|
|
2043
|
-
key = class_name.to_s
|
|
2506
|
+
key = class_name.to_s.delete_prefix("::")
|
|
2044
2507
|
return if warned[key]
|
|
2045
2508
|
|
|
2046
2509
|
warned[key] = true
|
|
2510
|
+
if @state[:internal_demand]
|
|
2511
|
+
@state[:definition_build_deferred_count] = @state.fetch(:definition_build_deferred_count, 0) + 1
|
|
2512
|
+
@state[:definition_build_deferred_first] ||= [class_name.to_s, error]
|
|
2513
|
+
return
|
|
2514
|
+
end
|
|
2515
|
+
|
|
2516
|
+
warn(definition_build_failure_warning(class_name, error))
|
|
2517
|
+
end
|
|
2518
|
+
|
|
2519
|
+
def definition_build_failure_warning(class_name, error)
|
|
2047
2520
|
first_line = error.message.to_s.lines.first.to_s.strip
|
|
2048
2521
|
buffers = definition_build_conflict_buffers(error)
|
|
2049
2522
|
collisions =
|
|
@@ -2056,13 +2529,42 @@ module Rigor
|
|
|
2056
2529
|
lines << " … and #{more} more" if more.positive?
|
|
2057
2530
|
"\n Colliding declaration(s):\n#{lines.join("\n")}"
|
|
2058
2531
|
end
|
|
2059
|
-
|
|
2060
|
-
"rigor: RBS definition build failed for `#{class_name}`: #{error.class}: #{first_line}\n " \
|
|
2532
|
+
"rigor: RBS definition build failed for `#{class_name}`: #{error.class}: #{first_line}\n " \
|
|
2061
2533
|
"Rigor still treats the class as known, so calls into it now silently degrade to\n " \
|
|
2062
2534
|
"`Dynamic[top]` — real methods and typos alike — instead of resolving normally.#{collisions}"
|
|
2535
|
+
end
|
|
2536
|
+
|
|
2537
|
+
def warn_about_deferred_definition_build_failures
|
|
2538
|
+
return if @state[:definition_build_summary_warned]
|
|
2539
|
+
|
|
2540
|
+
count = @state[:definition_build_deferred_count]
|
|
2541
|
+
return if count.nil? || count.zero?
|
|
2542
|
+
|
|
2543
|
+
@state[:definition_build_summary_warned] = true
|
|
2544
|
+
first_class, first_error = @state[:definition_build_deferred_first]
|
|
2545
|
+
affected = if count == 1
|
|
2546
|
+
"1 class affected"
|
|
2547
|
+
else
|
|
2548
|
+
more = count - 1
|
|
2549
|
+
noun = more == 1 ? "class" : "classes"
|
|
2550
|
+
"… and #{more} more #{noun} affected (#{count} total)"
|
|
2551
|
+
end
|
|
2552
|
+
warn(
|
|
2553
|
+
"#{definition_build_failure_warning(first_class, first_error)}\n " \
|
|
2554
|
+
"Internal whole-universe demand found #{affected}; the " \
|
|
2555
|
+
"`rbs.coverage.definition-build-failed` diagnostic reports the classes the analysis demanded and " \
|
|
2556
|
+
"the culprit member and signature files."
|
|
2063
2557
|
)
|
|
2064
2558
|
end
|
|
2065
2559
|
|
|
2560
|
+
def warn_about_aborted_definition_build_failures
|
|
2561
|
+
count = @state[:definition_build_deferred_count]
|
|
2562
|
+
return if count.nil? || count.zero?
|
|
2563
|
+
|
|
2564
|
+
first_class, first_error = @state[:definition_build_deferred_first]
|
|
2565
|
+
warn(definition_build_failure_warning(first_class, first_error))
|
|
2566
|
+
end
|
|
2567
|
+
|
|
2066
2568
|
# The definition-build twin of {#env_build_conflict_buffers}: the declaration source file(s) named by a
|
|
2067
2569
|
# `RBS::DefinitionBuilder` failure, so {#warn_about_definition_build_failure} can name the colliding
|
|
2068
2570
|
# declarations rather than only the exception's class and message. The payload shape varies by error
|
|
@@ -2105,8 +2607,6 @@ module Rigor
|
|
|
2105
2607
|
# `#method_name`; `UnknownMethodAliasError` and `NoSuperclassFoundError` have `#type_name`;
|
|
2106
2608
|
# `SuperclassMismatchError` has `#name`. `RecursiveAncestorError` has none of them and yields nil, and
|
|
2107
2609
|
# the diagnostic then omits the clause rather than inventing one.
|
|
2108
|
-
#
|
|
2109
|
-
# @return [String, nil]
|
|
2110
2610
|
def definition_build_member(error)
|
|
2111
2611
|
return error.qualified_method_name.to_s if error.respond_to?(:qualified_method_name)
|
|
2112
2612
|
|