rigortype 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +61 -5
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +192 -27
- data/docs/manual/03-configuration.md +38 -4
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +47 -3
- data/docs/manual/12-caching.md +9 -1
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +49 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +2 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +217 -52
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +15 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +154 -0
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +37 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +202 -43
- data/lib/rigor/analysis/path_expansion.rb +41 -0
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +7 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
- data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +233 -120
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +38 -33
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +60 -18
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +188 -93
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +27 -21
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +17 -13
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +14 -8
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +60 -4
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +22 -18
- data/lib/rigor/cli/plugins_renderer.rb +34 -8
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
- data/lib/rigor/cli/sig_gen_command.rb +130 -34
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +18 -12
- data/lib/rigor/cli/type_of_project_scope.rb +24 -0
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +17 -6
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +86 -34
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +592 -70
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +136 -32
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +44 -11
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +170 -0
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +204 -12
- data/lib/rigor/inference/definer_resolution.rb +21 -5
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +173 -57
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +110 -1
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +246 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +57 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +109 -29
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +23 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +162 -29
- data/lib/rigor/inference/narrowing.rb +113 -38
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_seed_tables.rb +2 -1
- data/lib/rigor/inference/project_yielder.rb +122 -0
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +614 -146
- data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +3036 -278
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +9 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +98 -4
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +12 -6
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +82 -3
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +26 -11
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +38 -16
- data/lib/rigor/plugin/registry.rb +41 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +96 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +25 -2
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +379 -36
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +668 -84
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +7 -6
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +21 -12
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +13 -16
- data/lib/rigor/type/constant.rb +48 -7
- data/lib/rigor/type/hash_shape.rb +26 -40
- data/lib/rigor/type/struct_instance.rb +3 -2
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +0 -2
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +10 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +6 -5
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +6 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/scope.rbs +51 -50
- data/sig/rigor/type.rbs +0 -6
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +55 -3
data/lib/rigor/environment.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative "environment/bundle_sig_discovery"
|
|
|
16
16
|
require_relative "environment/lockfile_resolver"
|
|
17
17
|
require_relative "environment/installed_gem_set"
|
|
18
18
|
require_relative "environment/rbs_collection_discovery"
|
|
19
|
+
require_relative "environment/project_path_gems"
|
|
19
20
|
require_relative "environment/source_rbs_synthesis"
|
|
20
21
|
require_relative "plugin/isolation"
|
|
21
22
|
require_relative "cache/engine_source"
|
|
@@ -39,7 +40,7 @@ module Rigor
|
|
|
39
40
|
DEFAULT_PROJECT_SIG_DIR = "sig"
|
|
40
41
|
private_constant :DEFAULT_PROJECT_SIG_DIR
|
|
41
42
|
|
|
42
|
-
# `DEFAULT_LIBRARIES` is defined in the light `environment/default_libraries.rb` (required
|
|
43
|
+
# `DEFAULT_LIBRARIES` is defined in the light `environment/default_libraries.rb` (required above) so the
|
|
43
44
|
# ADR-87 WD4 boot-slimming probe can read it without loading the inference engine.
|
|
44
45
|
|
|
45
46
|
# ADR-72 — a Gemfile.lock gem name mapped to the opt-in plugin id that ships the SAME core-ext RBS. When
|
|
@@ -146,7 +147,7 @@ module Rigor
|
|
|
146
147
|
# code (`Plugin::Registry#hkt_overlay_registry` reads each manifest); before this it sat one line ABOVE
|
|
147
148
|
# the #784 rescue, so a raise there escaped {#hkt_registry} entirely. Post-#788 that is not a per-file
|
|
148
149
|
# crash storm but an uncaught abort: {HktRegistryHolder#fetch} memoises only on success, so the raise
|
|
149
|
-
# is re-attempted at every demand, and the run-owned demands (`Runner::
|
|
150
|
+
# is re-attempted at every demand, and the run-owned demands (`Runner::RunLevelFacts::Collect.hkt_scan_failure`
|
|
150
151
|
# after the file loop, `Analysis::WorkerSession#drain_reporters`) sit outside any rescue.
|
|
151
152
|
#
|
|
152
153
|
# The degradation is narrower than the scan's: only the plugin overlay is dropped, and the RBS scan
|
|
@@ -265,10 +266,12 @@ module Rigor
|
|
|
265
266
|
# `nil` (the default), the canonical project layout `<root>/sig` is used if it exists, otherwise no
|
|
266
267
|
# signature path is loaded.
|
|
267
268
|
# @param cache_store — persistent cache threaded into the underlying
|
|
268
|
-
# {Environment::RbsLoader} so constant lookups
|
|
269
|
-
#
|
|
269
|
+
# {Environment::RbsLoader} so constant lookups consult the cache. Pass `nil` (the default) to skip
|
|
270
|
+
# caching for this environment.
|
|
271
|
+
# @param core_root — issue #2517 — a core signature directory loaded in place of the `rbs` gem's `core/`
|
|
272
|
+
# ({Configuration#core_root}); nil (the default) loads the gem's core.
|
|
270
273
|
# rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists
|
|
271
|
-
def for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil,
|
|
274
|
+
def for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil, # rubocop:disable Metrics/AbcSize
|
|
272
275
|
plugin_registry: nil, dependency_source_index: nil,
|
|
273
276
|
rbs_extended_reporter: nil, boundary_cross_reporter: nil,
|
|
274
277
|
source_rbs_synthesis_reporter: nil,
|
|
@@ -276,7 +279,8 @@ module Rigor
|
|
|
276
279
|
bundler_lockfile: nil,
|
|
277
280
|
rbs_collection_lockfile: nil, rbs_collection_auto_detect: false,
|
|
278
281
|
synthetic_method_index: nil, project_patched_methods: nil,
|
|
279
|
-
source_files: [], locked_gems: nil, required_features: nil
|
|
282
|
+
source_files: [], locked_gems: nil, required_features: nil, core_root: nil,
|
|
283
|
+
paths: nil, exclude_patterns: nil, exclude_base: nil)
|
|
280
284
|
resolved_paths = project_signature_roots(signature_paths, root: root)
|
|
281
285
|
# O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
|
|
282
286
|
# root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
|
|
@@ -318,7 +322,11 @@ module Rigor
|
|
|
318
322
|
# Issue #1700 — plus the gated vendored directories this run's source activates ({#feature_tokens}).
|
|
319
323
|
merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s) +
|
|
320
324
|
feature_tokens(required_features, source_files, libraries)).uniq
|
|
321
|
-
|
|
325
|
+
# Issue #2441 — plus the project's own path gems ({ProjectPathGems}), whose collection copy would
|
|
326
|
+
# double-declare the project's `sig/`.
|
|
327
|
+
project_gems = ProjectPathGems.names(locked, paths: paths, root: root, exclude_patterns: exclude_patterns,
|
|
328
|
+
exclude_base: exclude_base || root)
|
|
329
|
+
skip_gem_names = collection_skip_gem_names(merged_libraries) + project_gems.to_a
|
|
322
330
|
collection_paths = RbsCollectionDiscovery.discover(
|
|
323
331
|
lockfile_path: rbs_collection_lockfile,
|
|
324
332
|
project_root: root,
|
|
@@ -338,8 +346,10 @@ module Rigor
|
|
|
338
346
|
# opt-in plugin twin is REACHABLE — loaded as a plugin, or wired in by `signature_paths:` (#672) —
|
|
339
347
|
# so the two never both declare the methods. The paths ride in `loader_signature_paths`, so the env
|
|
340
348
|
# cache descriptor digests them for free.
|
|
349
|
+
# Issue #2441 — the project's own path gems are neither missing RBS nor overlay-eligible: the project's
|
|
350
|
+
# `sig/` declares them, and an overlay (`data/gem_overlay/activesupport`) would collide with it.
|
|
341
351
|
missing_gems, overlay_paths = missing_gems_and_overlay_paths(
|
|
342
|
-
locked: locked, default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
|
|
352
|
+
locked: locked.except(*project_gems), default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
|
|
343
353
|
rbs_collection_paths: collection_paths, plugin_registry: plugin_registry,
|
|
344
354
|
signature_paths: resolved_paths, bundle_root: bundle_root
|
|
345
355
|
)
|
|
@@ -364,7 +374,10 @@ module Rigor
|
|
|
364
374
|
# Issue #610 — the plugin-contributed subset is loaded LAST and may stand down against a colliding
|
|
365
375
|
# generic arity. Only the bundled plugins' own `sig/` is deferred: the user's `signature_paths:`,
|
|
366
376
|
# the bundle walk and `rbs collection install` all stay authoritative.
|
|
367
|
-
deferred_signature_paths: plugin_sig_paths
|
|
377
|
+
deferred_signature_paths: plugin_sig_paths,
|
|
378
|
+
project_signature_paths: resolved_paths,
|
|
379
|
+
required_features: required_features || RequiredFeatures.scan(source_files, sig_paths: resolved_paths),
|
|
380
|
+
core_root: core_root
|
|
368
381
|
)
|
|
369
382
|
# Issues #824 / #1075 — the rows ADR-112 WD5's consistency rule reports for members the project's
|
|
370
383
|
# own `sig/` and an inline source both declare: an error per contradiction (or per refinement that
|
|
@@ -374,6 +387,10 @@ module Rigor
|
|
|
374
387
|
# on every run, so the member-level outcome needs no plumbing of its own. Costs nothing for a project
|
|
375
388
|
# with no inline RBS: the loader's reader short-circuits on an empty `virtual_rbs`.
|
|
376
389
|
record_member_consistency(loader, source_rbs_synthesis_reporter, root)
|
|
390
|
+
# Issue #2517 — a selected core is built here, not lazily. The env is first demanded from inside a file's
|
|
391
|
+
# analysis, under rescues that turn any raise into one `internal analyzer error` row per file; building it
|
|
392
|
+
# now raises the {CoreRootError} once, before analysis, where the CLI reports it as a configuration error.
|
|
393
|
+
loader.verify_core_root!
|
|
377
394
|
# ADR-20 slice 2c + 2e — seed hkt_registry with the bundled builtins. The Environment's
|
|
378
395
|
# `#hkt_registry` getter then LAZILY merges in the RBS env scan on first call so fast paths that
|
|
379
396
|
# don't consult HKT (e.g. `rigor check --cache-stats --no-stats`) don't pay the eager env-build cost
|
|
@@ -490,7 +507,7 @@ module Rigor
|
|
|
490
507
|
end
|
|
491
508
|
|
|
492
509
|
# ADR-72 — resolve the bundled RBS overlay directories to load for this project. A gem is eligible when
|
|
493
|
-
# it is classified `:missing` (see {
|
|
510
|
+
# it is classified `:missing` (see {missing_rows}) and its conflicting opt-in plugin twin (if
|
|
494
511
|
# any) is NOT already reachable. Returns `[Pathname]`, deterministically ordered, or `[]` when no
|
|
495
512
|
# eligible gem.
|
|
496
513
|
#
|
|
@@ -642,6 +659,8 @@ module Rigor
|
|
|
642
659
|
def record_member_undecided(reporter, record, root)
|
|
643
660
|
plugin_id, path = split_virtual_source_name(record.virtual_name)
|
|
644
661
|
return if path.nil?
|
|
662
|
+
return record_variable_undecided(reporter, record, root, plugin_id, path) if
|
|
663
|
+
MemberConsistency.variable_kind?(record.kind)
|
|
645
664
|
|
|
646
665
|
reporter.record(
|
|
647
666
|
plugin_id: plugin_id, path: path, kind: :not_honoured,
|
|
@@ -654,8 +673,22 @@ module Rigor
|
|
|
654
673
|
)
|
|
655
674
|
end
|
|
656
675
|
|
|
676
|
+
# Issue #2440 — an instance variable declared by both sides with different types. `sig/` binds
|
|
677
|
+
# (the coordinator's ruling on #2440, ADR-5): the row is `:info`, never `rbs.contradicting-signature`.
|
|
678
|
+
def record_variable_undecided(reporter, record, root, plugin_id, path)
|
|
679
|
+
reporter.record(
|
|
680
|
+
plugin_id: plugin_id, path: path, kind: :not_honoured,
|
|
681
|
+
message: "`#{member_label(record)}` is also declared in " \
|
|
682
|
+
"`#{path_relative_to(record.signature_path, root)}` with a different type " \
|
|
683
|
+
"(#{record.detail}), so the `.rbs` declaration binds and the inline one was dropped. " \
|
|
684
|
+
"RBS rejects two declarations of one instance variable, so only one can stay; " \
|
|
685
|
+
"regenerate the committed `.rbs` (`rbs-inline --output`) or align the two types."
|
|
686
|
+
)
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# `Demo#m` / `Demo.m` for a method, `Demo#@x` / `Demo.@x` for an instance / class instance variable.
|
|
657
690
|
def member_label(record)
|
|
658
|
-
separator = record.kind
|
|
691
|
+
separator = %i[singleton class_instance_variable].include?(record.kind) ? "." : "#"
|
|
659
692
|
"#{record.class_name}#{separator}#{record.method_name}"
|
|
660
693
|
end
|
|
661
694
|
|
|
@@ -26,13 +26,13 @@ module Rigor
|
|
|
26
26
|
#
|
|
27
27
|
# ## Composition rules (ADR-2)
|
|
28
28
|
#
|
|
29
|
-
# - `:return_type` — Intersect via `Type::Combinator.intersection`; collapse to bot
|
|
30
|
-
# `:return_type_collapse
|
|
29
|
+
# - `:return_type` — Intersect via `Type::Combinator.intersection`; collapse to bot records a
|
|
30
|
+
# `:return_type_collapse` conflict.
|
|
31
31
|
# - `:truthy_fact` / `:falsey_fact` / `:post_return_fact` — Edge-local; accumulate while deduping by
|
|
32
32
|
# payload equality.
|
|
33
33
|
# - `:mutation` / `:invalidation` / `:role` — Union; dedupe by equality.
|
|
34
|
-
# - `:exception` — Single-valued. Two non-`nil` non-equal exceptional effects
|
|
35
|
-
# `:exceptional_disagreement
|
|
34
|
+
# - `:exception` — Single-valued. Two non-`nil` non-equal exceptional effects record an
|
|
35
|
+
# `:exceptional_disagreement` conflict.
|
|
36
36
|
#
|
|
37
37
|
# ## Cross-tier contradictions
|
|
38
38
|
#
|
|
@@ -75,41 +75,66 @@ module Rigor
|
|
|
75
75
|
}.freeze
|
|
76
76
|
private_constant :TYPE_HANDLERS
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
private_constant :
|
|
80
|
-
|
|
81
|
-
# Whether a `maybe` for `param` against `arg` rests on a
|
|
82
|
-
# `Hash` with a gradual arm ({#gradual_hash?}) or an open `HashShape
|
|
83
|
-
# of
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return false unless
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
UNREAD_SHAPE_REFUSED_KEY = :__rigor_acceptance_unread_shape_refused__
|
|
79
|
+
private_constant :UNREAD_SHAPE_REFUSED_KEY
|
|
80
|
+
|
|
81
|
+
# Whether a `maybe` for `param` against `arg` rests on a shape that could not read its argument: a record
|
|
82
|
+
# against a `Hash` with a gradual arm ({#gradual_hash?}) or an open `HashShape`, or a tuple against an
|
|
83
|
+
# `Array` of unknown arity ({#array_carrier?}). The shape cannot prove the key set or the length, and its
|
|
84
|
+
# `maybe` climbs through whatever holds it. `OverloadSelector`'s strict pass reads that `maybe` as no
|
|
85
|
+
# evidence for the overload, so this re-asks with each such shape answering `no` and reports whether the
|
|
86
|
+
# pair then fails. A `maybe` from anywhere else (a subclass only the Ruby source declares, a splat under a
|
|
87
|
+
# parameter that is no shape) survives the re-ask and still counts.
|
|
88
|
+
def maybe_rests_on_unread_shape?(param, arg)
|
|
89
|
+
return false unless rests_on_unread_hash?(param, arg) || rests_on_unread_array?(param, arg)
|
|
90
|
+
|
|
91
|
+
refusing_unread(:all) { accepts(param, arg, mode: :gradual).no? }
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
+
# Whether any of `types` holds an `Array` a tuple cannot read ({#array_carrier?}), so a match may rest on
|
|
95
|
+
# the tuple's `maybe`. `OverloadSelector`'s gradual pass asks before re-matching under
|
|
96
|
+
# {#refusing_unread_arrays}.
|
|
97
|
+
def unread_array_in?(types)
|
|
98
|
+
types.any? { |type| contains_type?(type) { |inner| array_carrier?(inner) } }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Runs the block with each tuple answering `no`, not `maybe`, for an `Array` of unknown arity. A record's
|
|
102
|
+
# `maybe` for an unread hash is untouched.
|
|
103
|
+
def refusing_unread_arrays(&) = refusing_unread(:array, &)
|
|
104
|
+
|
|
94
105
|
# rubocop:disable-next Metrics/ClassLength
|
|
95
106
|
class << self
|
|
96
107
|
private
|
|
97
108
|
|
|
98
|
-
|
|
109
|
+
def rests_on_unread_hash?(param, arg)
|
|
110
|
+
contains_type?(param) { |type| type.is_a?(Type::HashShape) } &&
|
|
111
|
+
contains_type?(arg) { |type| unread_hash?(type) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def rests_on_unread_array?(param, arg)
|
|
115
|
+
contains_type?(param) { |type| type.is_a?(Type::Tuple) } &&
|
|
116
|
+
contains_type?(arg) { |type| array_carrier?(type) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The flag lives on the thread, not on a parameter, because the shape may sit any number of `accepts`
|
|
99
120
|
# calls below the one asked (a tuple element, a `Hash` value, a union member), and those calls go
|
|
100
|
-
# through each carrier's `Type#accepts`. It is set only for the duration of the block.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Thread.current[
|
|
121
|
+
# through each carrier's `Type#accepts`. It is set only for the duration of the block. `kind` is `:all`
|
|
122
|
+
# (records and tuples) or `:array` (tuples only).
|
|
123
|
+
def refusing_unread(kind)
|
|
124
|
+
previous = Thread.current[UNREAD_SHAPE_REFUSED_KEY]
|
|
125
|
+
Thread.current[UNREAD_SHAPE_REFUSED_KEY] = kind
|
|
104
126
|
yield
|
|
105
127
|
ensure
|
|
106
|
-
Thread.current[
|
|
128
|
+
Thread.current[UNREAD_SHAPE_REFUSED_KEY] = previous
|
|
107
129
|
end
|
|
108
130
|
|
|
109
|
-
# The `maybe` a
|
|
110
|
-
# {#
|
|
111
|
-
def
|
|
112
|
-
|
|
131
|
+
# The `maybe` a shape of `kind` (`:hash` or `:array`) answers for an argument it cannot read, or `no`
|
|
132
|
+
# inside {#refusing_unread} for that kind.
|
|
133
|
+
def unread_shape_maybe(mode, reason, kind)
|
|
134
|
+
refused = Thread.current[UNREAD_SHAPE_REFUSED_KEY]
|
|
135
|
+
if refused == :all || refused == kind
|
|
136
|
+
return Type::AcceptsResult.no(mode: mode, reasons: "#{reason} (refused as evidence)")
|
|
137
|
+
end
|
|
113
138
|
|
|
114
139
|
Type::AcceptsResult.maybe(mode: mode, reasons: reason)
|
|
115
140
|
end
|
|
@@ -469,9 +494,10 @@ module Rigor
|
|
|
469
494
|
end
|
|
470
495
|
|
|
471
496
|
# Returns `other_type` rewritten so its type_args have the same arity as `self_type.type_args`, or
|
|
472
|
-
# `nil` if no projection is known. Today
|
|
473
|
-
# general RBS-driven implementation that consults
|
|
474
|
-
# subclass / module-include relations is the principled
|
|
497
|
+
# `nil` if no projection is known. Today the Hash → Enumerable and ArithmeticSequence → Enumerator
|
|
498
|
+
# projections are hand-rolled; a general RBS-driven implementation that consults
|
|
499
|
+
# `definition.ancestors[i].args` for arbitrary subclass / module-include relations is the principled
|
|
500
|
+
# follow-up.
|
|
475
501
|
def project_to_target_arity(self_type, other_type)
|
|
476
502
|
return nil if self_type.type_args.size == other_type.type_args.size
|
|
477
503
|
return nil if self_type.type_args.empty? || other_type.type_args.empty?
|
|
@@ -881,8 +907,8 @@ module Rigor
|
|
|
881
907
|
# `Refined[base, predicate]` accepts another type X when the base accepts the *base* of X *and* X is
|
|
882
908
|
# provably contained in the predicate's value set. The base check is delegated to
|
|
883
909
|
# `accepts(self.base, X.base)` so handlers like `accepts_nominal` see Nominal-vs-Nominal and return
|
|
884
|
-
# their normal answer (
|
|
885
|
-
#
|
|
910
|
+
# their normal answer (`accepts_nominal` projects a `Refined` / `Difference` other onto its base,
|
|
911
|
+
# which is what makes the comparison meaningful).
|
|
886
912
|
#
|
|
887
913
|
# Provability rules in gradual mode (the conservative analogue of `accepts_difference`):
|
|
888
914
|
#
|
|
@@ -1003,9 +1029,12 @@ module Rigor
|
|
|
1003
1029
|
|
|
1004
1030
|
# Tuple[A1..An] accepts:
|
|
1005
1031
|
# - Tuple[B1..Bn] when arities match and each Ai accepts Bi (covariant per-position).
|
|
1006
|
-
# -
|
|
1032
|
+
# - An `Array[E]` (see {#array_element_verdict}): maybe, or no on an element contradiction.
|
|
1033
|
+
# - Anything else: no.
|
|
1007
1034
|
def accepts_tuple(self_type, other_type, mode)
|
|
1008
1035
|
unless other_type.is_a?(Type::Tuple)
|
|
1036
|
+
return array_element_verdict(self_type, other_type, mode) if array_carrier?(other_type)
|
|
1037
|
+
|
|
1009
1038
|
return Type::AcceptsResult.no(
|
|
1010
1039
|
mode: mode,
|
|
1011
1040
|
reasons: "Tuple does not accept #{other_type.class}"
|
|
@@ -1025,6 +1054,38 @@ module Rigor
|
|
|
1025
1054
|
combine_arg_results(per_element, mode)
|
|
1026
1055
|
end
|
|
1027
1056
|
|
|
1057
|
+
# An `Array` nominal, directly or as the base of a `Difference` (`non-empty-array[E]`). Its arity is
|
|
1058
|
+
# unknown, so a tuple cannot reject it on length: `URI.encode_www_form` takes
|
|
1059
|
+
# `Enumerable[[_ToS, _ToS]]` and is handed the `Array[Array[String]]` that `URI.decode_www_form`
|
|
1060
|
+
# answers. An `Array` subclass is not read, as {#gradual_hash?} does not read a `Hash` subclass.
|
|
1061
|
+
def array_carrier?(type)
|
|
1062
|
+
nominal = type.is_a?(Type::Difference) ? type.base : type
|
|
1063
|
+
nominal.is_a?(Type::Nominal) && nominal.class_name == "Array"
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
# `maybe`, unless the element type rules the tuple out at any length: some position accepts no member
|
|
1067
|
+
# of `E` (`[Integer, Integer]` never takes an `Array[String]`). A raw `Array` stays `maybe`, and so does
|
|
1068
|
+
# an element with a gradual arm, since no position rejects a `Dynamic` member. The accepted cost (ADR-5)
|
|
1069
|
+
# is that a wrong-length `Array[E]` passed or returned where a tuple is declared goes unreported.
|
|
1070
|
+
def array_element_verdict(self_type, other_type, mode)
|
|
1071
|
+
nominal = other_type.is_a?(Type::Difference) ? other_type.base : other_type
|
|
1072
|
+
element = nominal.type_args.first
|
|
1073
|
+
unless element.nil?
|
|
1074
|
+
members = ContentJoin.union_members(element)
|
|
1075
|
+
refused = self_type.elements.find do |formal|
|
|
1076
|
+
members.all? { |member| accepts(formal, member, mode: mode).no? }
|
|
1077
|
+
end
|
|
1078
|
+
if refused
|
|
1079
|
+
return Type::AcceptsResult.no(
|
|
1080
|
+
mode: mode,
|
|
1081
|
+
reasons: "tuple member #{refused.describe(:short)} rejects #{element.describe(:short)}"
|
|
1082
|
+
)
|
|
1083
|
+
end
|
|
1084
|
+
end
|
|
1085
|
+
|
|
1086
|
+
unread_shape_maybe(mode, "tuple arity unknown for #{other_type.describe(:short)}", :array)
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1028
1089
|
# HashShape{k1: T1, ...} accepts another HashShape when every required key of self is required on
|
|
1029
1090
|
# the other side and Ti accepts Ui (depth covariant). Optional keys may be absent on the other side;
|
|
1030
1091
|
# when present, their values are checked. A closed self rejects known extra keys, and answers at most
|
|
@@ -1056,7 +1117,7 @@ module Rigor
|
|
|
1056
1117
|
result = combine_arg_results(per_entry, mode)
|
|
1057
1118
|
return result unless result.yes? && self_type.closed? && other_type.open?
|
|
1058
1119
|
|
|
1059
|
-
|
|
1120
|
+
unread_shape_maybe(mode, "HashShape closed target cannot check open source", :hash)
|
|
1060
1121
|
end
|
|
1061
1122
|
|
|
1062
1123
|
# A `Hash` nominal — directly or as the base of a `Difference` (`non-empty-hash[K, V]`) — that is raw or
|
|
@@ -1085,7 +1146,7 @@ module Rigor
|
|
|
1085
1146
|
refused = refused_value_keys(self_type, value_type, mode)
|
|
1086
1147
|
return hash_shape_no(mode, "Hash value type refused for required keys: #{refused.inspect}") if refused.any?
|
|
1087
1148
|
|
|
1088
|
-
|
|
1149
|
+
unread_shape_maybe(mode, "HashShape cannot check #{other_type.describe(:short)}", :hash)
|
|
1089
1150
|
end
|
|
1090
1151
|
|
|
1091
1152
|
def unholdable_keys(self_type, key_type, mode)
|
|
@@ -211,9 +211,8 @@ module Rigor
|
|
|
211
211
|
cursor
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
-
# `|*rest|` binds an Array of the leftover positional arguments
|
|
215
|
-
#
|
|
216
|
-
# `Array[Dynamic[Top]]`. Element-type precision for rest parameters is deferred (demand-gated).
|
|
214
|
+
# `|*rest|` binds an Array of the leftover positional arguments: `Array[Dynamic[Top]]`, unless an
|
|
215
|
+
# auto-splat answered a rest type ({BlockAutoSplat}). The expected types are per-position, not per-rest.
|
|
217
216
|
def bind_rest(params_node, bindings)
|
|
218
217
|
rest = params_node.rest
|
|
219
218
|
return unless rest.respond_to?(:name) && rest&.name
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "block_call_timing"
|
|
6
|
+
require_relative "closure_escape_analyzer"
|
|
7
|
+
require_relative "project_method_ownership"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
module Inference
|
|
11
|
+
# Issue #2303 — whether a project method's body may run the block it is called with more than once ON ONE PATH,
|
|
12
|
+
# for the expression-position block-value pass (`ExpressionTyper#block_may_repeat?` through
|
|
13
|
+
# {ProjectYielder.block_value_may_repeat?}). That pass lays the #587 (b) captured binding under a repeating block,
|
|
14
|
+
# which adds values a single run never reads, so a callee read as repeating when it is not reports diagnostics on
|
|
15
|
+
# correct code (`s = "a"; r = either(f) { t = s; s = nil; t }; r.upcase`). The rule here therefore errs toward
|
|
16
|
+
# "runs once", unlike {ProjectYielder.may_run_block_repeatedly?}, whose only asker widens a return type.
|
|
17
|
+
#
|
|
18
|
+
# The walk counts block runs per path, saturating at {MANY}:
|
|
19
|
+
#
|
|
20
|
+
# - a `yield`, or `call` / `yield` / `===` / `[]` on the `&blk` parameter, is one run;
|
|
21
|
+
# - a sequence sums, and a branch (`if` / `unless` / ternary / `case` / `case ... in`) takes the largest arm,
|
|
22
|
+
# so `f ? yield(1) : yield(2)` is one run;
|
|
23
|
+
# - a `return`, `break`, `next` or receiver-less `raise` / `fail` ends its path, so the `yield` after
|
|
24
|
+
# `return yield(1) if k` is a different path from the one before it;
|
|
25
|
+
# - the body of `while` / `until` / `for`, of `Kernel#loop`, or the block of a catalogued core iterator
|
|
26
|
+
# ({ClosureEscapeAnalyzer.iterator_name?}) is {MANY} when a run lies on a path that reaches the next
|
|
27
|
+
# iteration (falling through the body, or a `next`); a run only on a path that leaves it (`return`, `break`,
|
|
28
|
+
# `raise`) adds what that path holds, so `xs.each { |x| return yield(x) if x > 1 }` is one run;
|
|
29
|
+
# - a hand-off of the block (`g(&blk)`, `g(&)`, `...`) is {MANY};
|
|
30
|
+
# - a block under any other call runs its body once (`then { yield }`, `synchronize { yield }`,
|
|
31
|
+
# `File.open(f) { yield f }`), a lambda body is not run at its definition, and a nested `def` is not this
|
|
32
|
+
# method's body.
|
|
33
|
+
#
|
|
34
|
+
# Reading `&blk` other than calling it (`return unless blk`) is no run.
|
|
35
|
+
#
|
|
36
|
+
# A result is `[fall, total, nxt, brk]`: the most runs on a path that falls through the node, on any path
|
|
37
|
+
# through it, on a path that ends at a `next`, and on one that ends at a `break` (nil where no path does).
|
|
38
|
+
module BlockRunCount
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
MANY = 2
|
|
42
|
+
ZERO = [0, 0, nil, nil].freeze
|
|
43
|
+
ALWAYS = [MANY, MANY, nil, nil].freeze
|
|
44
|
+
BLOCK_RUN_CALLS = Set[:call, :yield, :===, :[]].freeze
|
|
45
|
+
LOOP_NODES = [Prism::WhileNode, Prism::UntilNode, Prism::ForNode].freeze
|
|
46
|
+
RAISING_CALLS = Set[:raise, :fail].freeze
|
|
47
|
+
private_constant :ZERO, :ALWAYS, :BLOCK_RUN_CALLS, :LOOP_NODES, :RAISING_CALLS
|
|
48
|
+
|
|
49
|
+
# Memoised per `def` node for the run ({ProjectMethodOwnership.memo}): every call site, and every pass over
|
|
50
|
+
# one, asks the same callee.
|
|
51
|
+
def may_repeat?(def_node, scope)
|
|
52
|
+
memo = ProjectMethodOwnership.memo(scope)[:block_runs]
|
|
53
|
+
return memo[def_node] if memo.key?(def_node)
|
|
54
|
+
|
|
55
|
+
memo[def_node] = runs(def_node.body, def_node.parameters&.block&.name)[1] >= MANY
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def runs(node, block_name)
|
|
59
|
+
case node
|
|
60
|
+
when nil, Prism::DefNode, Prism::LambdaNode then ZERO
|
|
61
|
+
when Prism::YieldNode then run_after(sequence(node.compact_child_nodes, block_name))
|
|
62
|
+
when Prism::ForwardingArgumentsNode then ALWAYS
|
|
63
|
+
when Prism::BlockArgumentNode then block_argument_runs(node, block_name)
|
|
64
|
+
when Prism::IfNode then branch(runs(node.predicate, block_name), [node.statements, node.subsequent], block_name)
|
|
65
|
+
when Prism::UnlessNode
|
|
66
|
+
branch(runs(node.predicate, block_name), [node.statements, node.else_clause], block_name)
|
|
67
|
+
when Prism::CaseNode, Prism::CaseMatchNode then case_runs(node, block_name)
|
|
68
|
+
when Prism::CallNode then call_runs(node, block_name)
|
|
69
|
+
else other_runs(node, block_name)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def other_runs(node, block_name)
|
|
74
|
+
result = sequence(node.compact_child_nodes, block_name)
|
|
75
|
+
case node
|
|
76
|
+
when *LOOP_NODES then iterated(result)
|
|
77
|
+
when Prism::ReturnNode then [nil, result[1], result[2], result[3]]
|
|
78
|
+
when Prism::NextNode then [nil, result[1], larger(result[2], result[0]), result[3]]
|
|
79
|
+
when Prism::BreakNode then [nil, result[1], result[2], larger(result[3], result[0])]
|
|
80
|
+
else result
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# A body run any number of times: {MANY} when a run reaches the next iteration, else the runs of the paths
|
|
85
|
+
# that leave it. It may run zero times or be left by a `break`, and the walk continues after it either way.
|
|
86
|
+
def iterated((fall, total, nxt, brk))
|
|
87
|
+
again = [fall, nxt].compact.max || 0
|
|
88
|
+
return ALWAYS if again.positive?
|
|
89
|
+
|
|
90
|
+
[[0, brk].compact.max, total, nil, nil]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# A body run once: a `next` or `break` in it ends the run, and the walk continues after the call.
|
|
94
|
+
def run_once((fall, total, nxt, brk))
|
|
95
|
+
[[fall, nxt, brk].compact.max, total, nil, nil]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def block_argument_runs(node, block_name)
|
|
99
|
+
expression = node.expression
|
|
100
|
+
forwards = expression.nil? || (expression.is_a?(Prism::LocalVariableReadNode) && expression.name == block_name)
|
|
101
|
+
forwards ? ALWAYS : runs(expression, block_name)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def sequence(nodes, block_name)
|
|
105
|
+
nodes.reduce(ZERO) do |acc, child|
|
|
106
|
+
break acc if acc[0].nil?
|
|
107
|
+
|
|
108
|
+
chain(acc, runs(child, block_name))
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# `first` then `second`; nothing follows a path that does not fall through `first`.
|
|
113
|
+
def chain(first, second)
|
|
114
|
+
fall, total, nxt, brk = first
|
|
115
|
+
return first if fall.nil?
|
|
116
|
+
|
|
117
|
+
[second[0] && sum(fall, second[0]), [total, sum(fall, second[1])].max,
|
|
118
|
+
larger(nxt, second[2] && sum(fall, second[2])), larger(brk, second[3] && sum(fall, second[3]))]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def run_after(result)
|
|
122
|
+
chain(result, [1, 1, nil, nil])
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# `head` runs first, then exactly one of `arms` (a nil arm is the path that takes none).
|
|
126
|
+
def branch(head, arms, block_name)
|
|
127
|
+
return head if head[0].nil?
|
|
128
|
+
|
|
129
|
+
arms.map { |arm| chain(head, runs(arm, block_name)) }.reduce do |a, b|
|
|
130
|
+
[larger(a[0], b[0]), [a[1], b[1]].max, larger(a[2], b[2]), larger(a[3], b[3])]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def case_runs(node, block_name)
|
|
135
|
+
clauses = node.conditions
|
|
136
|
+
tests = clauses.flat_map { |clause| clause.is_a?(Prism::WhenNode) ? clause.conditions : [clause.pattern] }
|
|
137
|
+
arms = clauses.map(&:statements) + [node.else_clause]
|
|
138
|
+
branch(sequence([node.predicate, *tests], block_name), arms, block_name)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def call_runs(node, block_name)
|
|
142
|
+
return run_after(runs(node.arguments, block_name)) if block_run_call?(node, block_name)
|
|
143
|
+
|
|
144
|
+
block = node.block
|
|
145
|
+
literal = block.is_a?(Prism::BlockNode)
|
|
146
|
+
head = sequence([node.receiver, node.arguments, (block unless literal)], block_name)
|
|
147
|
+
return [nil, head[1], head[2], head[3]] if raising_call?(node)
|
|
148
|
+
return head unless literal
|
|
149
|
+
|
|
150
|
+
body = runs(block.body, block_name)
|
|
151
|
+
chain(head, repeating_call?(node) ? iterated(body) : run_once(body))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def sum(left, right)
|
|
155
|
+
(left + right).clamp(0, MANY)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def larger(left, right)
|
|
159
|
+
[left, right].compact.max
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def repeating_call?(node)
|
|
163
|
+
ClosureEscapeAnalyzer.iterator_name?(node.name) || BlockCallTiming.kernel_loop?(node)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def raising_call?(node)
|
|
167
|
+
node.receiver.nil? && RAISING_CALLS.include?(node.name)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# `blk.call` and its spellings.
|
|
171
|
+
def block_run_call?(node, block_name)
|
|
172
|
+
receiver = node.receiver
|
|
173
|
+
!block_name.nil? && receiver.is_a?(Prism::LocalVariableReadNode) && receiver.name == block_name &&
|
|
174
|
+
BLOCK_RUN_CALLS.include?(node.name)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -16,7 +16,7 @@ module Rigor
|
|
|
16
16
|
# non-stores) vs memo-key normalization (collapse arg-granularity thrash — a signature whose distinct-key
|
|
17
17
|
# count far exceeds its body-eval savings).
|
|
18
18
|
#
|
|
19
|
-
#
|
|
19
|
+
# The cutoff categories, one per guard site:
|
|
20
20
|
#
|
|
21
21
|
# - {RECURSION_GUARD} — `ExpressionTyper#infer_user_method_return` detected a `(receiver, method)` cycle
|
|
22
22
|
# and returned `Dynamic[top]` (the de-facto recursion-depth budget, effective depth 1).
|
|
@@ -27,6 +27,8 @@ module Rigor
|
|
|
27
27
|
# fuel and fell back to the plain `(receiver, method)` guard (in-cycle call → `Dynamic[top]`).
|
|
28
28
|
# - {RECURSION_FIXPOINT_CAP} — the fixpoint return-summary iteration (ADR-55 slice 2) hit its 3-evaluation
|
|
29
29
|
# cap without converging and collapsed the summary to `untyped` (today's behaviour).
|
|
30
|
+
# - {BLOCK_WRITEBACK_CAP} — the block / loop write-back fixpoint ({BodyFixpoint.converge}) collapsed a
|
|
31
|
+
# local that had not converged to `Dynamic[top]`.
|
|
30
32
|
#
|
|
31
33
|
# Enabled only when `RIGOR_BUDGET_TRACE` is set (to any non-empty value) in the environment, or via
|
|
32
34
|
# {enable!} in tests. When disabled, {hit} is a single boolean check and returns immediately, so normal
|
|
@@ -52,6 +54,10 @@ module Rigor
|
|
|
52
54
|
# `Dynamic[top]` (the escaping-block floor). Shared by slice B's loop-body fixpoint and by slice C's
|
|
53
55
|
# content-evidence fixpoint, where the collapsed name is one element / key / value slot of a collection join.
|
|
54
56
|
BLOCK_WRITEBACK_CAP = :block_writeback_cap
|
|
57
|
+
# `CalleeMutationChain` discovered more than {CalleeMutationChain::MAX_NODES} nodes from one parameter and took
|
|
58
|
+
# the rest as mutating (the callee floor's FP-safe cut). A label: it may vary with the order, the floor does not
|
|
59
|
+
# (ADR-127).
|
|
60
|
+
CALLEE_CHAIN_CAP = :callee_chain_cap
|
|
55
61
|
|
|
56
62
|
# ADR-57 return-memo profile counters (not cutoffs — see the module doc). All bumped by
|
|
57
63
|
# `ExpressionTyper#infer_user_method_return` / `#compute_user_method_return`.
|
|
@@ -82,7 +88,7 @@ module Rigor
|
|
|
82
88
|
|
|
83
89
|
CATEGORIES = [
|
|
84
90
|
RECURSION_GUARD, ANCESTOR_WALK_LIMIT, HKT_FUEL_EXHAUSTED, RECURSION_UNROLL_FUEL,
|
|
85
|
-
RECURSION_FIXPOINT_CAP, BLOCK_WRITEBACK_CAP,
|
|
91
|
+
RECURSION_FIXPOINT_CAP, BLOCK_WRITEBACK_CAP, CALLEE_CHAIN_CAP,
|
|
86
92
|
MEMO_ENTRIES, MEMO_HITS, MEMO_MISSES, MEMO_BODY_EVALS,
|
|
87
93
|
MEMO_REFUSE_ON_STACK, MEMO_REFUSE_UNROLL, MEMO_REFUSE_TRANSIENT
|
|
88
94
|
].freeze
|
|
@@ -9,8 +9,8 @@ module Rigor
|
|
|
9
9
|
#
|
|
10
10
|
# Exception is the base of every Ruby error class (RuntimeError, StandardError, KeyError, …). The
|
|
11
11
|
# Init_Exception block in `references/ruby/error.c` registers the entire hierarchy in one pass, so
|
|
12
|
-
# the YAML carries 27 classes — but only the base `Exception` row is wired into `CATALOG_BY_CLASS
|
|
13
|
-
#
|
|
12
|
+
# the YAML carries 27 classes — but only the base `Exception` row is wired into `CATALOG_BY_CLASS`.
|
|
13
|
+
# A `RuntimeError` receiver hits the Exception arm via `is_a?(Exception)` and the
|
|
14
14
|
# catalog answers with the base-class entries; subclass-specific methods (`KeyError#receiver`,
|
|
15
15
|
# `NameError#name`, …) intentionally miss the lookup until a later slice routes per-subclass
|
|
16
16
|
# class_names.
|
|
@@ -16,11 +16,8 @@ module Rigor
|
|
|
16
16
|
#
|
|
17
17
|
# No mutation blocklist is needed. The numeric classes expose no foldable bang or indirect-mutator
|
|
18
18
|
# method that the static C classifier mis-attributes (every `:leaf` numeric method is a pure value
|
|
19
|
-
# computation), so the generic `MethodCatalog` loader
|
|
20
|
-
#
|
|
21
|
-
# `method_entry` / `load_catalog` copy of `MethodCatalog`; folding it onto the shared loader also
|
|
22
|
-
# picks up alias resolution (e.g. `Integer#magnitude` → `abs`, `Integer#inspect` → `to_s`), which the
|
|
23
|
-
# old bespoke loader silently dropped.
|
|
19
|
+
# computation), so the generic `MethodCatalog` loader covers it directly. Using the shared loader
|
|
20
|
+
# rather than a bespoke copy also gives it alias resolution (e.g. `Integer#magnitude` → `abs`).
|
|
24
21
|
NUMERIC_CATALOG = MethodCatalog.for_topic("numeric")
|
|
25
22
|
end
|
|
26
23
|
end
|
|
@@ -20,8 +20,8 @@ module Rigor
|
|
|
20
20
|
mutating_selectors: {
|
|
21
21
|
"Random" => Set[
|
|
22
22
|
# `rand_random` -> `random_real` / `random_ulong_limited` advance the MT state on the receiver
|
|
23
|
-
# (instance #rand) and on
|
|
24
|
-
# indirect mutator.
|
|
23
|
+
# (instance #rand) and on the process-wide default generator (singleton .rand). The classifier
|
|
24
|
+
# misses the indirect mutator.
|
|
25
25
|
:rand,
|
|
26
26
|
# `random_bytes` / `random_s_bytes` consume MT output the same way #rand does — every call
|
|
27
27
|
# mutates the underlying generator.
|