rigortype 0.3.8 → 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 +72 -1
- 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 +32 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +11 -0
- 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 +8 -24
- 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 +81 -22
- 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 +177 -26
- 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 +256 -88
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +133 -35
- data/lib/rigor/analysis/worker_session.rb +9 -10
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +155 -21
- 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 +2 -4
- 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_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +77 -32
- 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 +2 -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 +2 -2
- 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 +388 -59
- data/lib/rigor/environment.rb +105 -20
- 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 +4 -7
- 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 +42 -12
- 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 +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +61 -21
- 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 +115 -22
- 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 +4 -5
- 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 +5 -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 +26 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
data/lib/rigor/rbs_extended.rb
CHANGED
|
@@ -92,23 +92,60 @@ module Rigor
|
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
# The shared empty answers of the per-method readers. Dispatch consults the three readers on every RBS
|
|
96
|
+
# method it resolves (~150k each on the lib self-check) and nearly every method carries no `rigor:v1:`
|
|
97
|
+
# annotation, so a fresh `[]` / `{}` per decline was pure allocation. Callers never mutate a reader's
|
|
98
|
+
# result (`read_flow_contribution` selects into fresh arrays; the override map is documented read-only).
|
|
99
|
+
NO_DIRECTIVES = [].freeze
|
|
100
|
+
private_constant :NO_DIRECTIVES
|
|
101
|
+
NO_OVERRIDES = {}.freeze
|
|
102
|
+
private_constant :NO_OVERRIDES
|
|
103
|
+
|
|
104
|
+
# `%a{rigor:v1:inferred-return}` — the declaration states the member's presence and its parameters and
|
|
105
|
+
# says nothing about what it returns, so the return comes from the body. Normative in
|
|
106
|
+
# `docs/type-specification/rbs-extended.md`.
|
|
107
|
+
INFERRED_RETURN_DIRECTIVE = "rigor:v1:inferred-return"
|
|
108
|
+
|
|
95
109
|
module_function
|
|
96
110
|
|
|
111
|
+
# Whether `method_def` declares its return type inferred rather than contracted (issue #823).
|
|
112
|
+
#
|
|
113
|
+
# The producer is `rigor-rbs-inline`: inside a file that carries one annotation, upstream rbs-inline
|
|
114
|
+
# emits a `(untyped, …) -> untyped` skeleton for every unannotated `def` too, and an accepted signature
|
|
115
|
+
# outranks body inference — so one annotation used to retype every sibling in its file to `untyped`.
|
|
116
|
+
# The plugin now marks exactly the type slots it DEFAULTED, and {MethodDispatcher::RbsDispatch} declines
|
|
117
|
+
# a marked member so the call takes the same body-inference tier an undeclared method takes. Nothing
|
|
118
|
+
# else about the declaration changes: the class keeps its full method surface, `new` keeps its arity,
|
|
119
|
+
# and cross-file references still resolve.
|
|
120
|
+
#
|
|
121
|
+
# Reads `RBS::Definition::Method#annotations`, so it answers for an inherited member too, and a
|
|
122
|
+
# hand-written `.rbs` may carry the annotation for the same effect.
|
|
123
|
+
#
|
|
124
|
+
# @param method_def — an `RBS::Definition::Method`, or anything else answering `#annotations`.
|
|
125
|
+
def inferred_return?(method_def)
|
|
126
|
+
return false if method_def.nil?
|
|
127
|
+
|
|
128
|
+
annotations = method_def.annotations
|
|
129
|
+
return false if annotations.nil? || annotations.empty?
|
|
130
|
+
|
|
131
|
+
annotations.any? { |annotation| annotation.string.to_s.strip == INFERRED_RETURN_DIRECTIVE }
|
|
132
|
+
end
|
|
133
|
+
|
|
97
134
|
# Reads RBS::Extended predicate effects off `RBS::Definition::Method#annotations`. Returns the effects in
|
|
98
135
|
# source order; duplicates and unrecognised `rigor:v1:` directives are dropped. Returns an empty array (NEVER
|
|
99
136
|
# `nil`) for a method with no recognised annotations so callers can iterate unconditionally.
|
|
100
137
|
#
|
|
101
|
-
# @param environment
|
|
138
|
+
# @param environment — ADR-13 slice
|
|
102
139
|
# 3b. When provided, threads the plugin-supplied
|
|
103
140
|
# `name_scope:` and the per-run reporter through the
|
|
104
141
|
# annotation-parse path. `nil` (default) preserves the
|
|
105
142
|
# pre-slice-3b behaviour — no plugin resolvers consulted
|
|
106
143
|
# and no diagnostics accumulated.
|
|
107
144
|
def read_predicate_effects(method_def, environment: nil)
|
|
108
|
-
return
|
|
145
|
+
return NO_DIRECTIVES if method_def.nil?
|
|
109
146
|
|
|
110
147
|
annotations = method_def.annotations
|
|
111
|
-
return
|
|
148
|
+
return NO_DIRECTIVES if annotations.nil? || annotations.empty?
|
|
112
149
|
|
|
113
150
|
name_scope = environment&.name_scope
|
|
114
151
|
reporter = environment&.rbs_extended_reporter
|
|
@@ -127,10 +164,11 @@ module Rigor
|
|
|
127
164
|
end
|
|
128
165
|
|
|
129
166
|
# The right-hand side accepts either a Capitalised class name (with optional `~` negation, optional `::`
|
|
130
|
-
# prefix, qualified names) OR a
|
|
131
|
-
# `Builtins::ImportedRefinements::Parser` (bare names, `name[T]`,
|
|
132
|
-
#
|
|
133
|
-
# `refinement` capture
|
|
167
|
+
# prefix, qualified names) OR a refinement payload routed through
|
|
168
|
+
# `Builtins::ImportedRefinements::Parser` (bare kebab-case names, `name[T]`, the numeric range form
|
|
169
|
+
# `Integer[1..10]`, and the deprecated `name<min, max>`). The two arms share the same overall directive
|
|
170
|
+
# shape; the parser detects which form matched by looking at the `class_name` vs `refinement` capture
|
|
171
|
+
# groups. A class head followed by a bracketed range literal is a refinement (ADR-109), not a class name.
|
|
134
172
|
PREDICATE_DIRECTIVE_PATTERN = /
|
|
135
173
|
\A
|
|
136
174
|
rigor:v1:(?<directive>predicate-if-(?:true|false))
|
|
@@ -141,7 +179,7 @@ module Rigor
|
|
|
141
179
|
(?:
|
|
142
180
|
(?<class_name>(?:::)?[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)
|
|
143
181
|
|
|
|
144
|
-
(?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])
|
|
182
|
+
(?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])?|[A-Z][A-Za-z0-9_]*\[[^\]]*\.\.[^\]]*\])
|
|
145
183
|
)
|
|
146
184
|
\s*
|
|
147
185
|
\z
|
|
@@ -183,10 +221,10 @@ module Rigor
|
|
|
183
221
|
#
|
|
184
222
|
# See {.read_predicate_effects} for the `environment:` keyword contract.
|
|
185
223
|
def read_assert_effects(method_def, environment: nil)
|
|
186
|
-
return
|
|
224
|
+
return NO_DIRECTIVES if method_def.nil?
|
|
187
225
|
|
|
188
226
|
annotations = method_def.annotations
|
|
189
|
-
return
|
|
227
|
+
return NO_DIRECTIVES if annotations.nil? || annotations.empty?
|
|
190
228
|
|
|
191
229
|
name_scope = environment&.name_scope
|
|
192
230
|
reporter = environment&.rbs_extended_reporter
|
|
@@ -214,7 +252,7 @@ module Rigor
|
|
|
214
252
|
(?:
|
|
215
253
|
(?<class_name>(?:::)?[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)
|
|
216
254
|
|
|
|
217
|
-
(?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])
|
|
255
|
+
(?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])?|[A-Z][A-Za-z0-9_]*\[[^\]]*\.\.[^\]]*\])
|
|
218
256
|
)
|
|
219
257
|
\s*
|
|
220
258
|
\z
|
|
@@ -352,7 +390,8 @@ module Rigor
|
|
|
352
390
|
|
|
353
391
|
# The trailing payload supports the full refinement grammar in `Builtins::ImportedRefinements::Parser` — bare
|
|
354
392
|
# kebab-case names plus parameterised forms like `non-empty-array[Integer]`, `non-empty-hash[Symbol,
|
|
355
|
-
# Integer]`, and `int<5, 10>`. The directive head is consumed by the regex;
|
|
393
|
+
# Integer]`, `Integer[5..10]`, and the deprecated `int<5, 10>`. The directive head is consumed by the regex;
|
|
394
|
+
# the rest is forwarded to the
|
|
356
395
|
# refinement parser. Anything the parser cannot resolve falls back to nil so the call site keeps the
|
|
357
396
|
# RBS-declared return type.
|
|
358
397
|
RETURN_DIRECTIVE_PATTERN = /
|
|
@@ -449,7 +488,7 @@ module Rigor
|
|
|
449
488
|
|
|
450
489
|
# Returned for `rigor:v1:param: <name> <refinement>`. The parameter name is a Ruby identifier (Symbol); the
|
|
451
490
|
# type is any `Rigor::Type` the refinement parser resolves (bare kebab-case name, parameterised form, or
|
|
452
|
-
# `
|
|
491
|
+
# `Integer[a..b]` range, or the deprecated `int<a, b>` — the same grammar the `return:` directive accepts).
|
|
453
492
|
ParamOverride = Data.define(:param_name, :type)
|
|
454
493
|
|
|
455
494
|
# Reads every `rigor:v1:param: <name> <refinement>` directive off `RBS::Definition::Method#annotations` and
|
|
@@ -469,10 +508,10 @@ module Rigor
|
|
|
469
508
|
# argument-check purposes; passing a too-wide `Nominal[String]` argument is flagged as an argument-type
|
|
470
509
|
# mismatch at the call site.
|
|
471
510
|
def read_param_type_overrides(method_def, environment: nil)
|
|
472
|
-
return
|
|
511
|
+
return NO_DIRECTIVES if method_def.nil?
|
|
473
512
|
|
|
474
513
|
annotations = method_def.annotations
|
|
475
|
-
return
|
|
514
|
+
return NO_DIRECTIVES if annotations.nil? || annotations.empty?
|
|
476
515
|
|
|
477
516
|
name_scope = environment&.name_scope
|
|
478
517
|
reporter = environment&.rbs_extended_reporter
|
|
@@ -491,9 +530,10 @@ module Rigor
|
|
|
491
530
|
# frozen Hash<Symbol, Rigor::Type>; missing keys mean "use the RBS-declared type". Callers MUST treat the
|
|
492
531
|
# hash as read-only.
|
|
493
532
|
def param_type_override_map(method_def, environment: nil)
|
|
494
|
-
read_param_type_overrides(method_def, environment: environment)
|
|
495
|
-
|
|
496
|
-
|
|
533
|
+
overrides = read_param_type_overrides(method_def, environment: environment)
|
|
534
|
+
return NO_OVERRIDES if overrides.empty?
|
|
535
|
+
|
|
536
|
+
overrides.to_h { |o| [o.param_name, o.type] }.freeze
|
|
497
537
|
end
|
|
498
538
|
|
|
499
539
|
# The `is` glue word is optional so authors can write either `param: id is non-empty-string` (consistent with
|
|
@@ -708,9 +748,9 @@ module Rigor
|
|
|
708
748
|
# `RBS::Extended` conflict channel) rather than silently resolved. Returns `nil` when the list
|
|
709
749
|
# carries neither spelling.
|
|
710
750
|
#
|
|
711
|
-
# @param annotations
|
|
712
|
-
# @param owner_key
|
|
713
|
-
# @param source
|
|
751
|
+
# @param annotations — the node's annotations, in source order.
|
|
752
|
+
# @param owner_key — the method key (`Class#m` / `Class.m`) or class name the bound binds.
|
|
753
|
+
# @param source — {Effects::Envelope::SOURCES} member to stamp when the labelled spelling
|
|
714
754
|
# matched; `%a{pure}` always stamps `:pure_annotation`, and a class-level read overrides both.
|
|
715
755
|
def read_effect_envelope(annotations, owner_key:, source: :effect_annotation, registry: nil, reporter: nil)
|
|
716
756
|
return nil if annotations.nil? || annotations.empty?
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# {Reflection}'s second constant-resolution phase, split out for the reader rather than for the
|
|
5
|
+
# loader: `reflection.rb` asks what a whole spelling names under a series of prefixes, and this file
|
|
6
|
+
# asks what a PATH names segment by segment. Reopening the module rather than nesting under it keeps
|
|
7
|
+
# the walk inside the facade its callers already read, and lets it use the same private candidate
|
|
8
|
+
# lookups (`constant_type_at`, `enclosing_class_path`, `ancestor_constant_scopes`) the ladder uses.
|
|
9
|
+
module Reflection
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Issue #656 — Ruby's lookup for an as-written, unrooted constant PATH (`A::B`, `A::B::C`), which is
|
|
13
|
+
# a different question from "what does this whole string name under some prefix". Ruby resolves the
|
|
14
|
+
# FIRST segment through the ladder a bare name gets — `Module.nesting`, then the enclosing class's
|
|
15
|
+
# ancestors, then the top level — and then resolves each LATER segment inside the constant the
|
|
16
|
+
# previous segment produced, searching THAT constant's own ancestors in turn. So `A::B` written in
|
|
17
|
+
# `P::Guard` with `P::A < P::Base` names `P::Base::B`, while every whole-path candidate
|
|
18
|
+
# (`P::Guard::A::B`, `P::A::B`, `A::B`) either misses or names a real but different class.
|
|
19
|
+
#
|
|
20
|
+
# `known` decides the FINAL segment only, so the constant typer (any constant, values included) and
|
|
21
|
+
# `Inference::Narrowing`'s class-guard resolver (classes only) share one walk without sharing an
|
|
22
|
+
# acceptance test — the two cannot answer different names for the same spelling. Every earlier
|
|
23
|
+
# segment must name a namespace, because only a namespace can own the next one.
|
|
24
|
+
#
|
|
25
|
+
# Returns nil for a single-segment name — the callers' own ladders already own that case — and the
|
|
26
|
+
# moment any segment declines. Declining hands the caller back its unchanged fallback instead of a
|
|
27
|
+
# guess: a walk that finds the WRONG constant is worse than the wrong constant answered today
|
|
28
|
+
# (AGENTS.md § "Implementation Guidelines").
|
|
29
|
+
#
|
|
30
|
+
# Checked against MRI 4.0.5 rather than recollection: a later segment does NOT reach the top level
|
|
31
|
+
# for either a Class or a Module owner (`P::Mod::B` raises `NameError` where a top-level `B`
|
|
32
|
+
# exists), which is why {.constant_in_namespace} consults the owner's ancestors and stops. Rigor
|
|
33
|
+
# still lets its callers fall through to their own top-level rung there — that read raises at
|
|
34
|
+
# runtime and Rigor reports nothing about it either way, so retracting the resolution would trade a
|
|
35
|
+
# silent wrong answer for a silent absent one at no gain (the same reasoning as
|
|
36
|
+
# {.toplevel_first_constant_type}'s caller-derived rungs).
|
|
37
|
+
def resolve_constant_path_name(name, scope, &known)
|
|
38
|
+
segments = name.to_s.split("::")
|
|
39
|
+
return nil unless segments.size > 1
|
|
40
|
+
|
|
41
|
+
owner = path_head_owner(segments.first, scope)
|
|
42
|
+
last = segments.size - 1
|
|
43
|
+
segments.each_with_index do |segment, index|
|
|
44
|
+
next if index.zero?
|
|
45
|
+
return nil if owner.nil?
|
|
46
|
+
|
|
47
|
+
owner = constant_in_namespace(owner, segment, scope) do |candidate|
|
|
48
|
+
index == last ? known.call(candidate) : namespace_known?(candidate, scope)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
owner
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Step 2.5's rung as a type: the segment-wise name, then that name looked up like any other
|
|
55
|
+
# candidate so the answer comes from the same source-precedence order as every other rung.
|
|
56
|
+
def constant_path_type(name, scope)
|
|
57
|
+
qualified = resolve_constant_path_name(name, scope) { |candidate| constant_type_at(candidate, scope) }
|
|
58
|
+
qualified && constant_type_at(qualified, scope)
|
|
59
|
+
end
|
|
60
|
+
private_class_method :constant_path_type
|
|
61
|
+
|
|
62
|
+
# The namespace a path's FIRST segment names — the bare-name ladder, restricted to answers that can
|
|
63
|
+
# OWN the segments that follow. A value constant is not one of them, so it declines here rather
|
|
64
|
+
# than producing a namespace prefix no source knows.
|
|
65
|
+
def path_head_owner(head, scope)
|
|
66
|
+
hit = first_namespace_hit(lexical_nesting_chain(scope), head, scope)
|
|
67
|
+
return hit if hit
|
|
68
|
+
|
|
69
|
+
prefix = enclosing_class_path(scope)
|
|
70
|
+
unless prefix.nil? || prefix.empty?
|
|
71
|
+
hit = first_namespace_hit(bounded_ancestor_scopes(prefix, scope), head, scope)
|
|
72
|
+
return hit if hit
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
head if namespace_known?(head, scope)
|
|
76
|
+
end
|
|
77
|
+
private_class_method :path_head_owner
|
|
78
|
+
|
|
79
|
+
# {.first_constant_hit}'s namespace twin: the first `<entry>::<name>` that can own further segments.
|
|
80
|
+
def first_namespace_hit(entries, name, scope)
|
|
81
|
+
entries.each do |entry|
|
|
82
|
+
candidate = "#{entry}::#{name}"
|
|
83
|
+
return candidate if namespace_known?(candidate, scope)
|
|
84
|
+
end
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
private_class_method :first_namespace_hit
|
|
88
|
+
|
|
89
|
+
# `segment` looked up inside `owner` and then through `owner`'s own ancestors — Ruby's lookup for
|
|
90
|
+
# every segment after the first. `Object` is deliberately absent: MRI raises rather than reaching a
|
|
91
|
+
# top-level constant through an explicit `::` receiver.
|
|
92
|
+
def constant_in_namespace(owner, segment, scope)
|
|
93
|
+
candidate = "#{owner}::#{segment}"
|
|
94
|
+
return candidate if yield(candidate)
|
|
95
|
+
|
|
96
|
+
bounded_ancestor_scopes(owner, scope).each do |ancestor|
|
|
97
|
+
inherited = "#{ancestor}::#{segment}"
|
|
98
|
+
return inherited if yield(inherited)
|
|
99
|
+
end
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
private_class_method :constant_in_namespace
|
|
103
|
+
|
|
104
|
+
# How many of {.ancestor_constant_scopes}' entries one segment may consult, under
|
|
105
|
+
# `Scope::ANCESTOR_WALK_LIMIT` — the budget `Scope`'s method lookup already spends on this same
|
|
106
|
+
# graph, reused rather than re-chosen so one pathological ancestry costs one bound. The list
|
|
107
|
+
# itself is the memoised one step 2 builds, so this bounds the per-segment consultation the walk
|
|
108
|
+
# adds and not the graph. Reported through the shared counter, so a truncated walk is visible in a
|
|
109
|
+
# budget trace instead of looking like a plain miss.
|
|
110
|
+
def bounded_ancestor_scopes(class_name, scope)
|
|
111
|
+
scopes = ancestor_constant_scopes(class_name, scope)
|
|
112
|
+
return scopes if scopes.size <= Scope::ANCESTOR_WALK_LIMIT
|
|
113
|
+
|
|
114
|
+
Inference::BudgetTrace.hit(Inference::BudgetTrace::ANCESTOR_WALK_LIMIT)
|
|
115
|
+
scopes.first(Scope::ANCESTOR_WALK_LIMIT)
|
|
116
|
+
end
|
|
117
|
+
private_class_method :bounded_ancestor_scopes
|
|
118
|
+
|
|
119
|
+
# Whether `name` can OWN a constant. `class_known?` covers the RBS and registry classes and modules;
|
|
120
|
+
# the project tables cover a namespace the project declares with no RBS for it.
|
|
121
|
+
def namespace_known?(name, scope)
|
|
122
|
+
scope.environment.class_known?(name) || known_project_namespace?(name, scope)
|
|
123
|
+
end
|
|
124
|
+
private_class_method :namespace_known?
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/rigor/reflection.rb
CHANGED
|
@@ -51,9 +51,6 @@ module Rigor
|
|
|
51
51
|
|
|
52
52
|
module_function
|
|
53
53
|
|
|
54
|
-
# @param class_name [String, Symbol]
|
|
55
|
-
# @param scope [Rigor::Scope]
|
|
56
|
-
# @return [Boolean]
|
|
57
54
|
def class_known?(class_name, scope: Scope.empty)
|
|
58
55
|
return true if scope.discovered_classes.key?(class_name.to_s)
|
|
59
56
|
|
|
@@ -92,7 +89,7 @@ module Rigor
|
|
|
92
89
|
loader.project_declared_class?(class_name)
|
|
93
90
|
end
|
|
94
91
|
|
|
95
|
-
# @return
|
|
92
|
+
# @return one of `:equal`, `:subclass`, `:superclass`,
|
|
96
93
|
# `:disjoint`, `:unknown`.
|
|
97
94
|
def class_ordering(lhs, rhs, scope: Scope.empty)
|
|
98
95
|
scope.environment.class_ordering(lhs, rhs)
|
|
@@ -171,9 +168,16 @@ module Rigor
|
|
|
171
168
|
# § "Implementation Guidelines"). Only project ancestors are walked; an RBS-known superclass
|
|
172
169
|
# contributes no name here (see {.ancestor_constant_scopes}).
|
|
173
170
|
#
|
|
171
|
+
# Step 2.5 (#656) — the path read SEGMENT BY SEGMENT, which is the only rung that can answer a
|
|
172
|
+
# path whose head and tail have different owners. See {.resolve_constant_path_name}. It sits
|
|
173
|
+
# BELOW the whole-path rungs so no read that resolves today moves, and ABOVE step 3 because
|
|
174
|
+
# beating the top-level whole-string answer is the point: `A::B` inside `P::Guard` with
|
|
175
|
+
# `P::A < P::Base` is `P::Base::B`, and step 3's `A::B` is a real but different class.
|
|
176
|
+
#
|
|
174
177
|
# Step 3 — the bare name (top level).
|
|
175
178
|
first_constant_hit(lexical_nesting_chain(scope), name, scope) ||
|
|
176
179
|
ancestor_constant_type(name, scope, enclosing_class_path(scope)) ||
|
|
180
|
+
constant_path_type(name, scope) ||
|
|
177
181
|
constant_type_at(name, scope)
|
|
178
182
|
end
|
|
179
183
|
|
|
@@ -215,8 +219,12 @@ module Rigor
|
|
|
215
219
|
# dependabot-core — `docs/notes/20260905-toplevel-def-cref-movable-sites.md`). Rigor reports nothing
|
|
216
220
|
# about them either way, so retracting them would trade a silent wrong answer for a silent absent one at
|
|
217
221
|
# no gain. Making them FIRE is a separate question with its own false-positive budget.
|
|
222
|
+
#
|
|
223
|
+
# Issue #656's rung goes LAST here rather than second: for a top-level body Ruby's own first answer
|
|
224
|
+
# for `A::B` is the top level, and a whole-string hit there already means "`A` owns `B`". The
|
|
225
|
+
# segment-wise walk only has something to add once that misses.
|
|
218
226
|
def toplevel_first_constant_type(name, scope)
|
|
219
|
-
constant_type_at(name, scope) || caller_derived_constant_type(name, scope)
|
|
227
|
+
constant_type_at(name, scope) || caller_derived_constant_type(name, scope) || constant_path_type(name, scope)
|
|
220
228
|
end
|
|
221
229
|
private_class_method :toplevel_first_constant_type
|
|
222
230
|
|
|
@@ -242,11 +250,16 @@ module Rigor
|
|
|
242
250
|
singleton = env.singleton_for_name(candidate)
|
|
243
251
|
return singleton if singleton
|
|
244
252
|
|
|
253
|
+
# Issue #639 — the class-EXISTENCE edge. Without it a reference-only consumer (`Foo` with no call on
|
|
254
|
+
# it) recorded nothing, so deleting the declaring file left the warm `--incremental` run serving
|
|
255
|
+
# `singleton(Foo)` where `--no-cache` answers the honest unresolved type. Recorded ahead of the lookup
|
|
256
|
+
# rather than on the hit: every rung of the ladder spells the same last segment, which is the whole of
|
|
257
|
+
# the key, so one call covers the candidates instead of a branch around each.
|
|
258
|
+
scope.record_class_existence(candidate) if Analysis::DependencyRecorder.active?
|
|
245
259
|
in_source_class = scope.discovered_classes[candidate]
|
|
246
260
|
return in_source_class if in_source_class
|
|
247
261
|
|
|
248
|
-
in_source_value = scope.in_source_constants[candidate]
|
|
249
|
-
if in_source_value
|
|
262
|
+
if (in_source_value = scope.in_source_constants[candidate])
|
|
250
263
|
scope.record_constant_dependency(candidate) if Analysis::DependencyRecorder.active?
|
|
251
264
|
return in_source_value
|
|
252
265
|
end
|
|
@@ -465,15 +478,14 @@ module Rigor
|
|
|
465
478
|
end
|
|
466
479
|
private_class_method :rbs_loader_for
|
|
467
480
|
|
|
468
|
-
# @return
|
|
481
|
+
# @return true when the analyzed source contains a class / module declaration
|
|
469
482
|
# for the given name. Does NOT consult the RBS loader (use {.class_known?} for the
|
|
470
483
|
# union).
|
|
471
484
|
def discovered_class?(class_name, scope: Scope.empty)
|
|
472
485
|
scope.discovered_classes.key?(class_name.to_s)
|
|
473
486
|
end
|
|
474
487
|
|
|
475
|
-
# @
|
|
476
|
-
# @return [Boolean] true when the ScopeIndexer recorded a `def` for the given method on
|
|
488
|
+
# @return true when the ScopeIndexer recorded a `def` for the given method on
|
|
477
489
|
# the given class with the matching kind.
|
|
478
490
|
#
|
|
479
491
|
# ADR-46 — a MISS records a negative cross-file dependency, so a consumer whose analysis
|
|
@@ -497,3 +509,7 @@ module Rigor
|
|
|
497
509
|
end
|
|
498
510
|
end
|
|
499
511
|
end
|
|
512
|
+
|
|
513
|
+
# Loaded last: the segment-wise path walk reopens the module above and reads its private candidate
|
|
514
|
+
# lookups, so the facade has to exist first.
|
|
515
|
+
require_relative "reflection/constant_path"
|
data/lib/rigor/runtime/jit.rb
CHANGED
|
@@ -70,7 +70,7 @@ module Rigor
|
|
|
70
70
|
# call is what enabled YJIT (so the `?`-suffix predicate convention does
|
|
71
71
|
# not apply; hence the Naming/PredicateMethod exemption).
|
|
72
72
|
#
|
|
73
|
-
# @return
|
|
73
|
+
# @return true if this call enabled YJIT; false if it was a
|
|
74
74
|
# no-op (unavailable / opted out / already enabled).
|
|
75
75
|
def enable_now # rubocop:disable Naming/PredicateMethod
|
|
76
76
|
return false unless available?
|
|
@@ -90,8 +90,8 @@ module Rigor
|
|
|
90
90
|
# up front (unavailable / opted out / already enabled), so callers never
|
|
91
91
|
# pay for a thread that could only no-op.
|
|
92
92
|
#
|
|
93
|
-
# @param seconds
|
|
94
|
-
# @return
|
|
93
|
+
# @param seconds — the amortization deadline.
|
|
94
|
+
# @return the deadline thread, or nil when no-op up front.
|
|
95
95
|
def enable_after(seconds)
|
|
96
96
|
return nil unless available?
|
|
97
97
|
return nil if disabled?
|
|
@@ -143,7 +143,7 @@ module Rigor
|
|
|
143
143
|
# Re-arming goes through {enable_after}, so the child records the same
|
|
144
144
|
# absolute deadline it inherited and a grandchild fork carries it too.
|
|
145
145
|
#
|
|
146
|
-
# @return
|
|
146
|
+
# @return the child's deadline thread; nil when YJIT was
|
|
147
147
|
# enabled immediately, and nil when the call is a no-op up front
|
|
148
148
|
# (unavailable / opted out / already enabled — a child forked after the
|
|
149
149
|
# parent enabled inherits the enabled state and has nothing to do).
|
|
@@ -166,8 +166,6 @@ module Rigor
|
|
|
166
166
|
|
|
167
167
|
# The resolved {enable_after} deadline: {DEADLINE_ENV} when it parses to a
|
|
168
168
|
# non-negative float, else {DEFAULT_DEADLINE_SECONDS}.
|
|
169
|
-
#
|
|
170
|
-
# @return [Numeric]
|
|
171
169
|
def deadline_seconds
|
|
172
170
|
raw = ENV.fetch(DEADLINE_ENV, nil)
|
|
173
171
|
return DEFAULT_DEADLINE_SECONDS if raw.nil? || raw.empty?
|
|
@@ -176,12 +174,12 @@ module Rigor
|
|
|
176
174
|
value && value >= 0 ? value : DEFAULT_DEADLINE_SECONDS
|
|
177
175
|
end
|
|
178
176
|
|
|
179
|
-
# @return
|
|
177
|
+
# @return whether this Ruby exposes `RubyVM::YJIT.enable`.
|
|
180
178
|
def available?
|
|
181
179
|
defined?(RubyVM::YJIT.enable) ? true : false
|
|
182
180
|
end
|
|
183
181
|
|
|
184
|
-
# @return
|
|
182
|
+
# @return whether the opt-out env switch is set.
|
|
185
183
|
def disabled?
|
|
186
184
|
ENV[DISABLE_ENV] == "1"
|
|
187
185
|
end
|
|
@@ -27,10 +27,13 @@ module Rigor
|
|
|
27
27
|
:discovered_superclasses,
|
|
28
28
|
:discovered_header_nestings,
|
|
29
29
|
:discovered_includes,
|
|
30
|
+
:discovered_extends,
|
|
30
31
|
:discovered_class_sources,
|
|
31
32
|
:constant_sources,
|
|
32
33
|
:published_constant_names,
|
|
33
34
|
:local_constant_names,
|
|
35
|
+
:published_constant_alias_names,
|
|
36
|
+
:published_constant_ivars,
|
|
34
37
|
:data_member_layouts,
|
|
35
38
|
:struct_member_layouts,
|
|
36
39
|
:param_inferred_types,
|
|
@@ -52,6 +55,11 @@ module Rigor
|
|
|
52
55
|
# either kind.
|
|
53
56
|
METHOD_KIND_BOTH = :both
|
|
54
57
|
|
|
58
|
+
# Issue #728 — the key a `discovered_header_nestings` bucket stores its class-wide chain under, beside
|
|
59
|
+
# the per-ancestor-name entries. `nil` is used because every other key in a bucket is an ancestor name
|
|
60
|
+
# as written, and no ancestor name can be nil.
|
|
61
|
+
UNKEYED_HEADER_NESTING = nil
|
|
62
|
+
|
|
55
63
|
# The shared all-empty index `Scope.empty` (and every scope that never sees a seeding pass) points at — one
|
|
56
64
|
# allocation per process.
|
|
57
65
|
EMPTY = new(
|
|
@@ -86,8 +94,20 @@ module Rigor
|
|
|
86
94
|
# name — the peel is the nested spelling's answer, and it searched an `Admin::Base` that a compact
|
|
87
95
|
# `class Admin::Widget < Base` written at the top level never reaches. An absent entry means "not
|
|
88
96
|
# recorded" and keeps the peel, so a scope that never saw a declaration walk is unchanged.
|
|
97
|
+
#
|
|
98
|
+
# Issue #728 — the value is keyed by the ancestor NAME the site wrote, because a class's declaration
|
|
99
|
+
# sites need not share a cref: `class Foo < Base` at the top level and `class ::Foo; include Helper`
|
|
100
|
+
# inside `module Outer` resolve `Base` and `Helper` in different ones, and a single chain per class
|
|
101
|
+
# gave `Outer` to both. {UNKEYED_HEADER_NESTING} holds the union over every ancestor-naming site, for
|
|
102
|
+
# a name no site recorded under its own key.
|
|
89
103
|
discovered_header_nestings: EMPTY_TABLE,
|
|
90
104
|
discovered_includes: EMPTY_TABLE,
|
|
105
|
+
# Issue #898 — the singleton-side twin of `discovered_includes`: `{qualified class or module name =>
|
|
106
|
+
# [module names it `extend`s, as written]}`, built by the same `ScopeIndexer` walk that #526 already
|
|
107
|
+
# ran to fold an extended module's instance defs onto the extending class's singleton. #526 consumed
|
|
108
|
+
# the table inside the indexer and threw it away; `Narrowing` needs it to survive onto the scope,
|
|
109
|
+
# because `Singleton[C]`'s ancestry is exactly what `extend` writes and nothing else records it.
|
|
110
|
+
discovered_extends: EMPTY_TABLE,
|
|
91
111
|
discovered_class_sources: EMPTY_TABLE,
|
|
92
112
|
# Issue #644 — `{qualified constant name => Set[declaring file]}`, the write attribution behind the
|
|
93
113
|
# cross-file value-constant table. Read only by `Scope#record_constant_dependency` during ADR-46
|
|
@@ -102,6 +122,16 @@ module Rigor
|
|
|
102
122
|
# author cannot see. Both empty outside a runner-seeded scope, which makes the guard a no-op there.
|
|
103
123
|
published_constant_names: EMPTY_NAME_SET,
|
|
104
124
|
local_constant_names: EMPTY_NAME_SET,
|
|
125
|
+
# Issue #667 — the two per-file tables that carry a published constant's provenance across a COPY,
|
|
126
|
+
# which the name sets above cannot: they answer about a reference's spelling, and a copy has none.
|
|
127
|
+
# `published_constant_alias_names` is the last segments of the constants THIS FILE assigns straight
|
|
128
|
+
# from a foreign published one (`MODE2 = AppConfig::MODE`), which the local-declaration exemption
|
|
129
|
+
# would otherwise release. `published_constant_ivars` is `{class name => Set[ivar name]}` for an ivar
|
|
130
|
+
# whose class-ivar seed is such a copy (`@mode = AppConfig::MODE` in `initialize`), stamped onto the
|
|
131
|
+
# flow carrier at method entry. Both are seeded by `ScopeIndexer.index` and only when the project
|
|
132
|
+
# published something, so a project with no cross-file value constants pays no walk.
|
|
133
|
+
published_constant_alias_names: EMPTY_NAME_SET,
|
|
134
|
+
published_constant_ivars: EMPTY_TABLE,
|
|
105
135
|
data_member_layouts: EMPTY_TABLE,
|
|
106
136
|
struct_member_layouts: EMPTY_TABLE,
|
|
107
137
|
# ADR-67 WD3 — the call-site parameter-inference table, keyed by `[class_name, method_name, kind]` (the
|