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
|
@@ -6,47 +6,92 @@ module Rigor
|
|
|
6
6
|
# cannot surface at the point of failure (the parsers are fail-soft, returning `nil` so call sites fall back
|
|
7
7
|
# to the RBS-declared type).
|
|
8
8
|
#
|
|
9
|
-
# Owns
|
|
9
|
+
# Owns three event streams:
|
|
10
10
|
#
|
|
11
11
|
# - `#unresolved_payloads` — `rigor:v1:*` directive payloads the resolver could not turn into a
|
|
12
|
-
# {Rigor::Type}. Surface as `dynamic.rbs-extended.unresolved` `:info` diagnostics
|
|
12
|
+
# {Rigor::Type}. Surface as `dynamic.rbs-extended.unresolved` `:info` diagnostics, except an
|
|
13
|
+
# unresolved `conforms-to` interface, which the aggregator stamps `:warning` (#928).
|
|
13
14
|
# - `#lossy_projections` — shape-projection type functions (`pick_of` / `omit_of` / `partial_of` /
|
|
14
15
|
# `required_of` / `readonly_of`) applied to a carrier that does not preserve shape information (anything
|
|
15
16
|
# other than `Type::HashShape` / `Type::Tuple`). Surface as `dynamic.shape.lossy-projection` `:info`
|
|
16
17
|
# diagnostics.
|
|
18
|
+
# - `#hkt_directive_errors` — malformed `rigor:v1:hkt_register` / `rigor:v1:hkt_define` directives the
|
|
19
|
+
# ADR-20 parser declined. Surface as `dynamic.rbs-extended.hkt-directive-invalid` `:info` diagnostics.
|
|
20
|
+
# - `#deprecated_forms` — payload spellings the grammar still accepts for one deprecation window but no
|
|
21
|
+
# longer displays (ADR-109 WD3: the PHPStan-style `int<a, b>`). Surface as
|
|
22
|
+
# `dynamic.rbs-extended.deprecated-form` `:info` diagnostics naming the replacement.
|
|
17
23
|
#
|
|
18
24
|
# Mutable through the run; consumed once by {Rigor::Analysis::Runner} at end-of-run. Each event is
|
|
19
|
-
# deduplicated by `(payload,
|
|
20
|
-
# lossy-projection
|
|
25
|
+
# deduplicated by its whole entry — `(payload, path, line, column)` for unresolved, `(head, path, line,
|
|
26
|
+
# column)` for lossy-projection, `(message, path, line, column)` for an hkt directive, `(payload,
|
|
27
|
+
# replacement, path, line, column)` for a deprecated form — so a single annotation read from many call
|
|
28
|
+
# sites yields one diagnostic.
|
|
21
29
|
#
|
|
22
30
|
# The reporter is intentionally thread-safe via a coarse `Mutex` because the inference engine may read the
|
|
23
31
|
# same method definition from multiple files in parallel; the critical sections are short (Array#include? +
|
|
24
32
|
# Array#<<) so the lock contention is negligible.
|
|
25
33
|
class Reporter
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
# Every entry carries its position as `(path, line, column)` primitives, flattened from the parser's
|
|
35
|
+
# `RBS::Location` by {.position_of} at record time. Issues #785 (hkt) and #805 (the two older streams).
|
|
36
|
+
# Two reasons, and either alone decides it. All three streams are drained out of every fork-pool worker
|
|
37
|
+
# ({Rigor::Analysis::WorkerSession#drain_reporters}), and an `RBS::Location` is a C-extension object with
|
|
38
|
+
# no `_dump`, so a `Marshal.dump` of the drain payload raises `TypeError` — which kills the worker at
|
|
39
|
+
# drain time and degrades the run to in-process re-analysis. And an `RBS::Location` compares equal only
|
|
40
|
+
# against a location over the SAME `RBS::Buffer` object, so two workers that each read the same `.rbs`
|
|
41
|
+
# would hand the coordinator two entries the dedup cannot collapse — `--workers=N` would then print N
|
|
42
|
+
# copies of a row `--workers=0` prints once.
|
|
43
|
+
#
|
|
44
|
+
# The record methods take the triple rather than the location for the same reason: a door that accepts
|
|
45
|
+
# an `RBS::Location` is a door a future caller reintroduces the bug through.
|
|
46
|
+
UnresolvedEntry = Data.define(:payload, :path, :line, :column)
|
|
47
|
+
LossyProjectionEntry = Data.define(:head, :path, :line, :column)
|
|
48
|
+
HktDirectiveEntry = Data.define(:message, :path, :line, :column)
|
|
49
|
+
DeprecatedFormEntry = Data.define(:payload, :replacement, :path, :line, :column)
|
|
50
|
+
|
|
51
|
+
# Flattens an `RBS::Location` (or anything answering the same readers) to the `(path, line, column)`
|
|
52
|
+
# triple every entry carries. `column` is 1-based, since `RBS::Location#start_column` is 0-based and
|
|
53
|
+
# diagnostics are not. Each component is `nil` when the location cannot supply it, and the diagnostic
|
|
54
|
+
# then falls back to `.rigor.yml:1:1`.
|
|
55
|
+
#
|
|
56
|
+
# Fail-soft by construction, like every parser that calls it: a location that raises while being read
|
|
57
|
+
# costs the entry its position, never the run.
|
|
58
|
+
def self.position_of(source_location)
|
|
59
|
+
return [nil, nil, nil] if source_location.nil?
|
|
60
|
+
|
|
61
|
+
buffer = source_location.respond_to?(:buffer) ? source_location.buffer : nil
|
|
62
|
+
name = buffer.respond_to?(:name) ? buffer.name.to_s : ""
|
|
63
|
+
line = source_location.respond_to?(:start_line) ? source_location.start_line : nil
|
|
64
|
+
column = source_location.respond_to?(:start_column) ? source_location.start_column + 1 : nil
|
|
65
|
+
[name.empty? ? nil : name, line, column]
|
|
66
|
+
rescue StandardError
|
|
67
|
+
[nil, nil, nil]
|
|
68
|
+
end
|
|
28
69
|
|
|
29
70
|
def initialize
|
|
30
71
|
@unresolved_payloads = []
|
|
31
72
|
@lossy_projections = []
|
|
73
|
+
@hkt_directive_errors = []
|
|
74
|
+
@deprecated_forms = []
|
|
32
75
|
@mutex = Mutex.new
|
|
33
76
|
end
|
|
34
77
|
|
|
35
|
-
# @return
|
|
78
|
+
# @return frozen snapshot of the accumulated unresolved-payload events.
|
|
36
79
|
def unresolved_payloads
|
|
37
80
|
@mutex.synchronize { @unresolved_payloads.dup.freeze }
|
|
38
81
|
end
|
|
39
82
|
|
|
40
|
-
# @return
|
|
83
|
+
# @return frozen snapshot of the accumulated lossy-projection events.
|
|
41
84
|
def lossy_projections
|
|
42
85
|
@mutex.synchronize { @lossy_projections.dup.freeze }
|
|
43
86
|
end
|
|
44
87
|
|
|
45
|
-
# Records a `dynamic.rbs-extended.unresolved` event. The
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
def record_unresolved(payload:,
|
|
49
|
-
entry = UnresolvedEntry.new(
|
|
88
|
+
# Records a `dynamic.rbs-extended.unresolved` event. The position triple is the source annotation's
|
|
89
|
+
# `.rbs` file / line / 1-based column — {.position_of} flattens the caller's `RBS::Location` into it —
|
|
90
|
+
# each `nil` when the caller had no location (the diagnostic then falls back to `.rigor.yml:1:1`).
|
|
91
|
+
def record_unresolved(payload:, path: nil, line: nil, column: nil)
|
|
92
|
+
entry = UnresolvedEntry.new(
|
|
93
|
+
payload: frozen_text(payload), path: frozen_text(path), line: line, column: column
|
|
94
|
+
)
|
|
50
95
|
@mutex.synchronize do
|
|
51
96
|
return if @unresolved_payloads.include?(entry)
|
|
52
97
|
|
|
@@ -56,8 +101,11 @@ module Rigor
|
|
|
56
101
|
|
|
57
102
|
# Records a `dynamic.shape.lossy-projection` event for one of the five shape-projection heads. `head` MUST
|
|
58
103
|
# be a String (`"pick_of"`, `"omit_of"`, …); the diagnostic message identifies which projection degraded.
|
|
59
|
-
|
|
60
|
-
|
|
104
|
+
# The position triple is read exactly as {#record_unresolved}'s is.
|
|
105
|
+
def record_lossy_projection(head:, path: nil, line: nil, column: nil)
|
|
106
|
+
entry = LossyProjectionEntry.new(
|
|
107
|
+
head: frozen_text(head), path: frozen_text(path), line: line, column: column
|
|
108
|
+
)
|
|
61
109
|
@mutex.synchronize do
|
|
62
110
|
return if @lossy_projections.include?(entry)
|
|
63
111
|
|
|
@@ -65,10 +113,68 @@ module Rigor
|
|
|
65
113
|
end
|
|
66
114
|
end
|
|
67
115
|
|
|
116
|
+
# @return frozen snapshot of the accumulated hkt-directive failures.
|
|
117
|
+
def hkt_directive_errors
|
|
118
|
+
@mutex.synchronize { @hkt_directive_errors.dup.freeze }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Records a `dynamic.rbs-extended.hkt-directive-invalid` event: one malformed ADR-20 HKT directive the
|
|
122
|
+
# {Rigor::RbsExtended::HktDirectives} parser declined. `message` names what the parser objected to; the
|
|
123
|
+
# position triple is the annotation's `.rbs` file / line / 1-based column, each `nil` when the caller had
|
|
124
|
+
# no location (the diagnostic then falls back to `.rigor.yml:1:1`).
|
|
125
|
+
#
|
|
126
|
+
# Every String is frozen individually, not just the enclosing `Data`: the entry crosses the pool drain
|
|
127
|
+
# channel, whose stated invariant is that its payload is `Ractor.shareable?` as well as Marshal-clean.
|
|
128
|
+
def record_hkt_error(message:, path: nil, line: nil, column: nil)
|
|
129
|
+
entry = HktDirectiveEntry.new(
|
|
130
|
+
message: frozen_text(message), path: frozen_text(path), line: line, column: column
|
|
131
|
+
)
|
|
132
|
+
@mutex.synchronize do
|
|
133
|
+
return if @hkt_directive_errors.include?(entry)
|
|
134
|
+
|
|
135
|
+
@hkt_directive_errors << entry
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @return frozen snapshot of the accumulated deprecated-form events.
|
|
140
|
+
def deprecated_forms
|
|
141
|
+
@mutex.synchronize { @deprecated_forms.dup.freeze }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Records a `dynamic.rbs-extended.deprecated-form` event (ADR-109 WD3): `payload` is the spelling the
|
|
145
|
+
# author wrote (`int<5, 10>`), `replacement` the spelling the same type displays as (`Integer[5..10]`),
|
|
146
|
+
# so the row can say exactly what to write instead. The position triple is read exactly as
|
|
147
|
+
# {#record_unresolved}'s is, and every String is frozen for the pool drain.
|
|
148
|
+
def record_deprecated_form(payload:, replacement:, path: nil, line: nil, column: nil)
|
|
149
|
+
entry = DeprecatedFormEntry.new(
|
|
150
|
+
payload: frozen_text(payload), replacement: frozen_text(replacement),
|
|
151
|
+
path: frozen_text(path), line: line, column: column
|
|
152
|
+
)
|
|
153
|
+
@mutex.synchronize do
|
|
154
|
+
return if @deprecated_forms.include?(entry)
|
|
155
|
+
|
|
156
|
+
@deprecated_forms << entry
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
68
160
|
# True when no events have accumulated. Used by callers that want to skip the diagnostic-emission pass
|
|
69
161
|
# entirely on the common no-event path.
|
|
70
162
|
def empty?
|
|
71
|
-
@mutex.synchronize
|
|
163
|
+
@mutex.synchronize do
|
|
164
|
+
@unresolved_payloads.empty? && @lossy_projections.empty? && @hkt_directive_errors.empty? &&
|
|
165
|
+
@deprecated_forms.empty?
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private
|
|
170
|
+
|
|
171
|
+
# `nil` stays `nil` — the diagnostic falls back to `.rigor.yml:1:1` on a missing path. Anything else
|
|
172
|
+
# becomes an individually frozen String: the `Data` wrapper is frozen on its own, but the pool drain's
|
|
173
|
+
# stated invariant is that its payload is `Ractor.shareable?`, which is a DEEP freeze. Coercing here also
|
|
174
|
+
# means a caller that hands the door an `RBS::Location` by mistake stores its `#to_s`, not the object —
|
|
175
|
+
# the entry cannot become un-Marshalable from the outside.
|
|
176
|
+
def frozen_text(value)
|
|
177
|
+
value.nil? ? nil : value.to_s.dup.freeze
|
|
72
178
|
end
|
|
73
179
|
end
|
|
74
180
|
end
|
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?
|
|
@@ -812,10 +852,15 @@ module Rigor
|
|
|
812
852
|
# ADR-13 slice 3b — guards every reporter call so the in-RbsExtended-module call sites can record events
|
|
813
853
|
# uniformly without nil-checking each time. When the reporter is nil (the v0.1.0 → v0.1.3 default for call
|
|
814
854
|
# sites that do not yet thread `environment:`), the call is a no-op and the parser stays fail-soft.
|
|
855
|
+
#
|
|
856
|
+
# Issue #805 — the location is flattened to `(path, line, column)` here, at record time, because the
|
|
857
|
+
# entry is drained out of every fork-pool worker and an `RBS::Location` neither survives `Marshal.dump`
|
|
858
|
+
# nor compares equal across two workers' buffers (see {Rigor::RbsExtended::Reporter}).
|
|
815
859
|
def record_unresolved(reporter, payload, source_location)
|
|
816
860
|
return if reporter.nil?
|
|
817
861
|
|
|
818
|
-
|
|
862
|
+
path, line, column = Reporter.position_of(source_location)
|
|
863
|
+
reporter.record_unresolved(payload: payload, path: path, line: line, column: column)
|
|
819
864
|
end
|
|
820
865
|
end
|
|
821
866
|
end
|
|
@@ -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
|