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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../run_scoped_slots"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
# The value the statement evaluator last gave a loop node, and the scope it evaluated the loop from, so
|
|
8
|
+
# `ExpressionTyper#type_of_loop` reads it instead of evaluating the loop again (issue #2031 review). A loop's value
|
|
9
|
+
# costs an evaluation of its body, its fixpoint and its `break` passes included, and the typer types a loop again
|
|
10
|
+
# wherever it types a node that holds one: the value of a write that wraps it (`id(z = (while …))`), a call it is
|
|
11
|
+
# the receiver of (`(while …).itself`), each narrowing and rule that types that operand. Each such typing of a
|
|
12
|
+
# loop nested in another loop re-ran the inner loop's passes, once more per level of nesting.
|
|
13
|
+
#
|
|
14
|
+
# The record is reused only for the very scope object the loop was evaluated from, so it answers exactly what an
|
|
15
|
+
# evaluation would. It is not compared with `Scope#==`, which ignores several fields (`match_frame`,
|
|
16
|
+
# `declared_refinements`, `struct_fold_safe_locals`, the opaque-block flags, `lexical_nesting`, the origins tables,
|
|
17
|
+
# `discovery`): across the cost spec's 10933 reads every match was an identity match, and `lib/` read no record at
|
|
18
|
+
# all (issue #2262). It lives in a per-thread map keyed by the loop node. The map's keys are weak but its values
|
|
19
|
+
# are strong, and a recorded scope reaches its discovery, its environment and the parse tree that holds the key,
|
|
20
|
+
# so a record never goes on its own (issue #2412): the map is a run-scoped slot ({RunScopedSlots}), emptied when
|
|
21
|
+
# the run that filled it ends.
|
|
22
|
+
module LoopValueRecord
|
|
23
|
+
KEY = RunScopedSlots.register(:rigor_loop_value_record)
|
|
24
|
+
private_constant :KEY
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
COUNT_KEY = RunScopedSlots.register(:rigor_loop_value_count)
|
|
29
|
+
private_constant :COUNT_KEY
|
|
30
|
+
|
|
31
|
+
def record(node, scope, type)
|
|
32
|
+
Thread.current[COUNT_KEY] = loops_evaluated + 1
|
|
33
|
+
conditional(node, scope, type)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# How many loops this thread has evaluated, so a conditional can tell whether its evaluation held one.
|
|
37
|
+
def loops_evaluated = Thread.current[COUNT_KEY] || 0
|
|
38
|
+
|
|
39
|
+
# The same record for a conditional that evaluated a loop in a branch: `id(c ? (while …) : 0)` types the
|
|
40
|
+
# conditional again from the scope it was evaluated from, and each typing evaluated the loop again from a
|
|
41
|
+
# branch scope of its own, which multiplied the passes of every loop nested in it (issue #2262).
|
|
42
|
+
def conditional(node, scope, type)
|
|
43
|
+
(Thread.current[KEY] ||= ObjectSpace::WeakKeyMap.new)[node] = [scope, type]
|
|
44
|
+
type
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The recorded value of `node` evaluated from `scope` itself, or nil.
|
|
48
|
+
def fetch(node, scope)
|
|
49
|
+
slot = Thread.current[KEY]&.[](node)
|
|
50
|
+
return nil if slot.nil?
|
|
51
|
+
|
|
52
|
+
recorded, type = slot
|
|
53
|
+
recorded.equal?(scope) ? type : nil
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -15,6 +15,12 @@ module Rigor
|
|
|
15
15
|
# `$_` (issue #1359), whose frame-wide answers ({#last_line_closure?}, {#last_line_fallback?}) are kept here
|
|
16
16
|
# too, asked only while `$_` is narrowed.
|
|
17
17
|
class Frame
|
|
18
|
+
# The body the frame runs, which issue #2555's escape scan reads ({ConstructionEscape.escaped?}).
|
|
19
|
+
attr_reader :body
|
|
20
|
+
# The method's parameters (nil for a class, module or file body), which issue #2608's holder scan reads for a
|
|
21
|
+
# zero-argument `super` ({RemoverHolderScan}).
|
|
22
|
+
attr_reader :parameters
|
|
23
|
+
|
|
18
24
|
# The nodes that bind a local in a body: the writes, and a block's, lambda's, `rescue`'s or pattern's own
|
|
19
25
|
# parameters and targets.
|
|
20
26
|
LOCAL_BINDINGS = Set[
|
|
@@ -37,6 +43,8 @@ module Rigor
|
|
|
37
43
|
@scans = nil
|
|
38
44
|
@last_line_closure = nil
|
|
39
45
|
@last_line_fallback = nil
|
|
46
|
+
@body_locals = nil
|
|
47
|
+
@parameter_names = nil
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
def matching_closure?(scope = nil)
|
|
@@ -88,6 +96,29 @@ module Rigor
|
|
|
88
96
|
@forwarded_block == name
|
|
89
97
|
end
|
|
90
98
|
|
|
99
|
+
# Issue #2354 — whether `name` is a local of the frame's own body: the body binds it (a write, or a block's,
|
|
100
|
+
# lambda's, `rescue`'s or pattern's own parameter or target) and no parameter of the method names it. A
|
|
101
|
+
# method parameter is the caller's object wherever the body also writes the name (`h = h.dup if f`, a
|
|
102
|
+
# later `h = {}`), so it never counts. Kept per name for the rest of the body.
|
|
103
|
+
def body_local?(name)
|
|
104
|
+
bound = (@body_locals ||= {})
|
|
105
|
+
return bound[name] if bound.key?(name)
|
|
106
|
+
|
|
107
|
+
bound[name] = !parameter_names.include?(name) && binds_local?(@body, name)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Issue #1157 — whether a call's `&expr` block argument hands on the block THIS method was called with: the
|
|
111
|
+
# anonymous `&` of a method that declares an anonymous `&` parameter, or {#forwarded_block?} for `&name`.
|
|
112
|
+
# Anything else (`&:sym`, a proc made here, a rebound name) is a block of this frame's own making.
|
|
113
|
+
def forwards_block?(block_argument)
|
|
114
|
+
expression = block_argument.expression
|
|
115
|
+
case expression
|
|
116
|
+
when nil then anonymous_block_parameter?
|
|
117
|
+
when Prism::LocalVariableReadNode then forwarded_block?(expression.name)
|
|
118
|
+
else false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
91
122
|
# The scan of `node` under `scope`, kept while `scope`'s local and instance-variable tables are the same
|
|
92
123
|
# objects: a lookup argument's answer reads them ({MatchRebinding.may_match?}), and a rebuild that leaves
|
|
93
124
|
# them alone passes the same tables on. `kind` keeps another reading of the same node apart (issue #1365:
|
|
@@ -125,6 +156,40 @@ module Rigor
|
|
|
125
156
|
name
|
|
126
157
|
end
|
|
127
158
|
|
|
159
|
+
# True when the method declares a bare `&` parameter, which only an anonymous `&` argument can read.
|
|
160
|
+
def anonymous_block_parameter?
|
|
161
|
+
parameters = @parameters
|
|
162
|
+
block = parameters.is_a?(Prism::ParametersNode) ? parameters.block : nil
|
|
163
|
+
!block.nil? && block.name.nil?
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The names the method's own parameters bind: positional (destructured ones included), optional, rest,
|
|
167
|
+
# keyword, keyword-rest and block. Empty for a class, module or file body.
|
|
168
|
+
def parameter_names
|
|
169
|
+
@parameter_names ||= begin
|
|
170
|
+
parameters = @parameters
|
|
171
|
+
names = Set.new
|
|
172
|
+
if parameters.is_a?(Prism::ParametersNode)
|
|
173
|
+
[*parameters.requireds, *parameters.optionals, parameters.rest, *parameters.posts,
|
|
174
|
+
*parameters.keywords, parameters.keyword_rest, parameters.block].each do |parameter|
|
|
175
|
+
collect_parameter_names(parameter, names)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
names.freeze
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def collect_parameter_names(node, names)
|
|
183
|
+
case node
|
|
184
|
+
when Prism::MultiTargetNode
|
|
185
|
+
[*node.lefts, node.rest, *node.rights].each { |target| collect_parameter_names(target, names) }
|
|
186
|
+
when Prism::SplatNode then collect_parameter_names(node.expression, names)
|
|
187
|
+
else
|
|
188
|
+
name = node.respond_to?(:name) ? node.name : nil
|
|
189
|
+
names << name if name
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
128
193
|
# The method's own `&block` parameter's name, or nil.
|
|
129
194
|
def block_parameter_name
|
|
130
195
|
parameters = @parameters
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MatchRebinding
|
|
8
|
+
# Issue #2443 — the match globals a block enters with when the call that owns it matches before every yield.
|
|
9
|
+
# `String#scan`, `#gsub`, `#gsub!`, `#sub` and `#sub!` set the caller's `$~` to the current match before each
|
|
10
|
+
# run of their block, with a Regexp pattern or a String one, so the block reads that match on every run
|
|
11
|
+
# whatever the enclosing frame held; the frame-wide entry ({MatchRebinding.block_entry}) would otherwise leave
|
|
12
|
+
# it the narrowing at the call site (issue #1371) or nothing at all, and `Regexp.last_match.begin(0)` in the
|
|
13
|
+
# block read `MatchData?`.
|
|
14
|
+
#
|
|
15
|
+
# A receiver that is a String, or a `Dynamic` the analyzer cannot type ({Narrowing.string_match_receiver?}),
|
|
16
|
+
# enters with the call's own match edge ({Narrowing.yielded_match_scope}). A `Dynamic` receiver may be another
|
|
17
|
+
# object whose block-taking `scan` runs no match, and then the block reads the caller's `$~`; narrowing it
|
|
18
|
+
# anyway can only drop a diagnostic, never add one (ADR-5). A receiver known to be anything else — a project
|
|
19
|
+
# class, `Pathname`, a union with either — keeps the frame-wide entry: a method defined in Ruby runs its
|
|
20
|
+
# matches in a frame of its own, so its block reads the caller's `$~` as the frame rules leave it. So does an
|
|
21
|
+
# implicit-self call.
|
|
22
|
+
module YieldingMatch
|
|
23
|
+
NAMES = Set[:scan, :gsub, :gsub!, :sub, :sub!].freeze
|
|
24
|
+
private_constant :NAMES
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# `scope` with the match globals `call_node`'s block enters with; `scope` itself for any other block.
|
|
29
|
+
# `operand_scope` types the receiver.
|
|
30
|
+
def entry(scope, block_node, call_node, operand_scope)
|
|
31
|
+
return scope unless yielding_call?(block_node, call_node) &&
|
|
32
|
+
Narrowing.string_match_receiver?(operand_scope.type_of(call_node.receiver), operand_scope)
|
|
33
|
+
|
|
34
|
+
Narrowing.yielded_match_scope(scope)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def yielding_call?(block_node, call_node)
|
|
38
|
+
call_node.is_a?(Prism::CallNode) && NAMES.include?(call_node.name) && !call_node.receiver.nil? &&
|
|
39
|
+
block_node.is_a?(Prism::BlockNode) && call_node.block.equal?(block_node)
|
|
40
|
+
end
|
|
41
|
+
private_class_method :yielding_call?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -12,6 +12,7 @@ require_relative "match_rebinding/frame"
|
|
|
12
12
|
require_relative "match_rebinding/operands"
|
|
13
13
|
require_relative "match_rebinding/calls"
|
|
14
14
|
require_relative "match_rebinding/self_calls"
|
|
15
|
+
require_relative "match_rebinding/yielding_match"
|
|
15
16
|
require_relative "last_line"
|
|
16
17
|
|
|
17
18
|
module Rigor
|
|
@@ -367,12 +368,21 @@ module Rigor
|
|
|
367
368
|
#
|
|
368
369
|
# Issue #1429 — the entry also restores a guard's narrowing of a global or constant where the body may run after
|
|
369
370
|
# code that rebinds it ({GuardRebinding.block_entry}).
|
|
371
|
+
#
|
|
372
|
+
# Issue #2443 — the block of a call that matches before every yield (`scan`, `gsub`, `sub`, …) on a String or
|
|
373
|
+
# untyped receiver enters with that call's own match edge, laid over the entry above ({YieldingMatch}); a match
|
|
374
|
+
# its body runs still rebinds them through the statement-level rules.
|
|
370
375
|
def block_entry(scope, block_node, call_node = nil)
|
|
376
|
+
operand_scope = scope
|
|
371
377
|
scope = GuardRebinding.block_entry(scope, block_node, call_node)
|
|
372
378
|
return FreshFrameBlocks.entry(scope, call_node) if FreshFrameBlocks.fresh_entry?(call_node, scope)
|
|
373
379
|
|
|
374
380
|
scope = FreshFrameBlocks.closure_entry(scope, block_node, call_node)
|
|
375
381
|
scope = LastLine.block_entry(scope, block_node, call_node)
|
|
382
|
+
YieldingMatch.entry(frame_entry(scope, block_node, call_node), block_node, call_node, operand_scope)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def frame_entry(scope, block_node, call_node)
|
|
376
386
|
return scope unless scope.match_globals_bound?
|
|
377
387
|
return scope unless scope.match_rebinding_closure? || entry_may_match?(block_node.body, scope, call_node) ||
|
|
378
388
|
(call_node.is_a?(Prism::CallNode) && operands_may_rebind?(call_node, scope))
|
|
@@ -386,7 +396,7 @@ module Rigor
|
|
|
386
396
|
|
|
387
397
|
body.is_a?(Prism::Node) && scan(body, scope, base: true)
|
|
388
398
|
end
|
|
389
|
-
private_class_method :entry_may_match?
|
|
399
|
+
private_class_method :frame_entry, :entry_may_match?
|
|
390
400
|
end
|
|
391
401
|
end
|
|
392
402
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
# ADR-127 WD3 — the callee floor's memo audit. A memo hit is correct only when the key holds every input the value
|
|
6
|
+
# was computed from, and a key that misses one answers a caller with another caller's value (#2516, #2524). With the
|
|
7
|
+
# audit on, every hit in the callee floor's memos (`resolved_census`, the unrecorded hand-off memo, the successor
|
|
8
|
+
# store and `def_census`) recomputes the value and raises {Mismatch} when it differs from the stored one, so a key
|
|
9
|
+
# defect fails on the first hit instead of waiting for an order that exposes it.
|
|
10
|
+
#
|
|
11
|
+
# Like {BudgetTrace}, a switch for specs and CI (`RIGOR_MEMO_AUDIT=1` in the environment, read once at load, or
|
|
12
|
+
# {enable!}): off by default, a single boolean check when off. A recomputation
|
|
13
|
+
# runs with the audit suspended on its thread and the memos bypassed ({suspended?}), so it reads nothing stored.
|
|
14
|
+
module MemoAudit
|
|
15
|
+
# A memo hit whose stored value differs from a fresh computation.
|
|
16
|
+
class Mismatch < StandardError; end
|
|
17
|
+
|
|
18
|
+
SUSPENDED_KEY = :rigor_memo_audit_suspended
|
|
19
|
+
|
|
20
|
+
@enabled = %w[1 true].include?(ENV["RIGOR_MEMO_AUDIT"].to_s.downcase)
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def enabled? = @enabled
|
|
25
|
+
|
|
26
|
+
# True while an audit recomputation runs on this thread. The callee floor's memos are then bypassed (read and
|
|
27
|
+
# write), so the recomputation reads no stored entry of another memo: a defect shared across a table (a stale
|
|
28
|
+
# `resolved_census(:edges)` entry) would otherwise make the recomputed value match the stored one.
|
|
29
|
+
def suspended? = Thread.current[SUSPENDED_KEY]
|
|
30
|
+
|
|
31
|
+
def enable! = (@enabled = true)
|
|
32
|
+
|
|
33
|
+
def disable! = (@enabled = false)
|
|
34
|
+
|
|
35
|
+
# `stored`, after checking it against `yield` when the audit is on. `label` and the key parts (`key`, `key2`) name
|
|
36
|
+
# the memo entry in the failure; they are passed apart so an audit that is off allocates nothing.
|
|
37
|
+
def check(label, stored, key, key2 = nil, &)
|
|
38
|
+
return stored unless @enabled && !Thread.current[SUSPENDED_KEY]
|
|
39
|
+
|
|
40
|
+
fresh = suspended(&)
|
|
41
|
+
return stored if fresh == stored
|
|
42
|
+
|
|
43
|
+
raise Mismatch, "#{label} memo hit differs from a fresh computation for #{[key, key2].inspect[0, 120]}: " \
|
|
44
|
+
"stored #{stored.inspect[0, 200]}, fresh #{fresh.inspect[0, 200]}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def suspended
|
|
48
|
+
Thread.current[SUSPENDED_KEY] = true
|
|
49
|
+
yield
|
|
50
|
+
ensure
|
|
51
|
+
Thread.current[SUSPENDED_KEY] = nil
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -7,7 +7,7 @@ module Rigor
|
|
|
7
7
|
# Canonical RBS-core aliases shipped by `core/builtin.rbs` whose body is `<Nominal> | _DuckType`.
|
|
8
8
|
# Matching an overload against an Integer literal should pick the `(int) -> Array[Elem]` body over
|
|
9
9
|
# the `(string) -> String` body because Integer satisfies `int`'s strict arm and not `string`'s.
|
|
10
|
-
# The translator
|
|
10
|
+
# The translator reads each alias's interface arm as `Dynamic[Top]` (interfaces are not structurally matched
|
|
11
11
|
# yet), so a dedicated pass 1.5 between strict and gradual consults this map to pick the alias
|
|
12
12
|
# whose strict arm matches.
|
|
13
13
|
#
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# The positional argument lists a call with a precise union argument stands for (#2379): one per combination
|
|
9
|
+
# of the union arguments' members, since the runtime selects an overload per value. `RbsDispatch` selects an
|
|
10
|
+
# overload for each list and joins their returns.
|
|
11
|
+
module ArgumentDistribution
|
|
12
|
+
# The most lists {.lists} spells out when more than one argument is a union. One union argument is linear in
|
|
13
|
+
# its members and is not capped.
|
|
14
|
+
LIMIT = 8
|
|
15
|
+
private_constant :LIMIT
|
|
16
|
+
|
|
17
|
+
# {.lists}' answer past {LIMIT}: the call's overloads cannot be read per value, and it answers `Dynamic[top]`.
|
|
18
|
+
OVER_LIMIT = :over_limit
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# The lists, nil when no argument is a union, or {OVER_LIMIT}.
|
|
23
|
+
def lists(arg_types)
|
|
24
|
+
unions = arg_types.count { |arg| arg.is_a?(Type::Union) }
|
|
25
|
+
return nil if unions.zero?
|
|
26
|
+
|
|
27
|
+
if unions > 1
|
|
28
|
+
count = arg_types.reduce(1) { |product, arg| arg.is_a?(Type::Union) ? product * arg.members.size : product }
|
|
29
|
+
return OVER_LIMIT if count > LIMIT
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
arg_types.reduce([[]]) do |partials, arg|
|
|
33
|
+
members = arg.is_a?(Type::Union) ? arg.members : [arg]
|
|
34
|
+
partials.flat_map { |partial| members.map { |member| partial + [member] } }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -317,7 +317,7 @@ module Rigor
|
|
|
317
317
|
end
|
|
318
318
|
|
|
319
319
|
# Returns the receiver's known finite element count, or nil when the carrier does not pin a size.
|
|
320
|
-
# Tuple and HashShape are pinned by construction
|
|
320
|
+
# Tuple and HashShape are pinned by construction, and a `Constant` Range by its bounds. Other
|
|
321
321
|
# shapes (Array[T], Range[T], Nominal) decline so the RBS tier widens.
|
|
322
322
|
def finite_size(receiver)
|
|
323
323
|
case receiver
|
|
@@ -192,8 +192,8 @@ module Rigor
|
|
|
192
192
|
|
|
193
193
|
# #2115 — the numeric twin of `STRING_FOLD_BYTE_LIMIT`. A folded Integer (and each Integer part of a
|
|
194
194
|
# folded Rational / Complex) is at most this many bits, the largest bit length whose decimal
|
|
195
|
-
# rendering still fits in `STRING_FOLD_BYTE_LIMIT` digits: `2 ** 13_605` folds,
|
|
196
|
-
# (bit length 13_607) does not. Computing `2 ** 100_000_000` takes Ruby milliseconds, but
|
|
195
|
+
# rendering still fits in `STRING_FOLD_BYTE_LIMIT` digits: `2 ** 13_605` folds,
|
|
196
|
+
# `2 ** 13_606` (bit length 13_607) does not. Computing `2 ** 100_000_000` takes Ruby milliseconds, but
|
|
197
197
|
# rendering it in decimal — which describing, sorting and printing the `Constant` does — takes
|
|
198
198
|
# minutes. `**` / `pow` / `<<` / `>>` also check their operands before invoking, so the big value
|
|
199
199
|
# is never computed.
|
|
@@ -219,6 +219,15 @@ module Rigor
|
|
|
219
219
|
# declared `Numeric`, on which `(2 ** 20_000).even?` is an undefined method.
|
|
220
220
|
WidenedResult = Data.define(:class_name)
|
|
221
221
|
|
|
222
|
+
# #2125 — a String result past `STRING_FOLD_BYTE_LIMIT`, or an operand that would build one. Unlike a
|
|
223
|
+
# `WidenedResult` it declines the WHOLE fold, every other member of a union receiver included: dropping
|
|
224
|
+
# only the oversized member would answer the rest (`(c ? "a" : big) * 2` read `"aa"`). The next tier then
|
|
225
|
+
# answers the call — `LiteralStringFolding`'s `literal-string`, or RBS's `String`, never less than the
|
|
226
|
+
# `Nominal[String]` the fold could have widened to.
|
|
227
|
+
OversizedString = Data.define
|
|
228
|
+
OVERSIZED_STRING = [OversizedString.new].freeze
|
|
229
|
+
private_constant :OversizedString, :OVERSIZED_STRING
|
|
230
|
+
|
|
222
231
|
# Input cartesian product hard cap. Keeps fold cost bounded even when the receiver and argument are
|
|
223
232
|
# both `Union[Constant…]`. 5 × 5 = 25 inputs is permitted; 6 × 6 = 36 is not. The user-facing payoff
|
|
224
233
|
# (a precise small enum) drops off fast past this range and CRuby method invocation cost adds up.
|
|
@@ -331,6 +340,7 @@ module Rigor
|
|
|
331
340
|
arg = args.first
|
|
332
341
|
ruby_arg = format_argument_value(arg)
|
|
333
342
|
return nil if ruby_arg.nil?
|
|
343
|
+
return nil if format_blow_up?(receiver.value, ruby_arg)
|
|
334
344
|
|
|
335
345
|
result = receiver.value % ruby_arg
|
|
336
346
|
return nil unless foldable_constant_value?(result)
|
|
@@ -370,7 +380,7 @@ module Rigor
|
|
|
370
380
|
# Normalises an input type into one of:
|
|
371
381
|
# - `Array<Object>` for a `Constant` (1-element) or `Union[Constant…]` (n-element) — concrete values
|
|
372
382
|
# to enumerate.
|
|
373
|
-
# - `Type::IntegerRange` — bounded interval.
|
|
383
|
+
# - `Type::IntegerRange` / `Type::FloatRange` — bounded interval.
|
|
374
384
|
# - `nil` — the input shape is not foldable.
|
|
375
385
|
def numeric_set_of(type)
|
|
376
386
|
case type
|
|
@@ -1200,6 +1210,8 @@ module Rigor
|
|
|
1200
1210
|
# "Integer-ness" was already implied by the inputs (so the widening fallback only fires for
|
|
1201
1211
|
# arithmetic over integers).
|
|
1202
1212
|
def build_constant_type(values, source: nil)
|
|
1213
|
+
return nil if values.any?(OversizedString)
|
|
1214
|
+
|
|
1203
1215
|
widened, values = values.partition { |value| value.is_a?(WidenedResult) }
|
|
1204
1216
|
return build_scalar_constant_type(values, source) if widened.empty?
|
|
1205
1217
|
|
|
@@ -1213,7 +1225,8 @@ module Rigor
|
|
|
1213
1225
|
def build_scalar_constant_type(values, source)
|
|
1214
1226
|
return nil if values.empty?
|
|
1215
1227
|
|
|
1216
|
-
|
|
1228
|
+
# #1513 — keyed so the possible results `0.0` and `-0.0` stay two members, as `Constant#==` keeps them.
|
|
1229
|
+
unique = values.uniq { |value| Type::Constant.value_key(value) }
|
|
1217
1230
|
return collapse_constants(unique) if unique.size <= UNION_FOLD_OUTPUT_LIMIT
|
|
1218
1231
|
|
|
1219
1232
|
widen_to_integer_range(unique, source) ||
|
|
@@ -1643,6 +1656,7 @@ module Rigor
|
|
|
1643
1656
|
# `flat_map`.
|
|
1644
1657
|
def invoke_binary(receiver_value, method_name, arg_value)
|
|
1645
1658
|
return nil unless safe?(receiver_value, method_name, arg_value)
|
|
1659
|
+
return OVERSIZED_STRING if string_blow_up?(receiver_value, method_name, arg_value)
|
|
1646
1660
|
return nil if range_enumeration_blow_up?(receiver_value, method_name)
|
|
1647
1661
|
|
|
1648
1662
|
widened = numeric_blow_up_class(receiver_value, method_name, arg_value)
|
|
@@ -1658,6 +1672,7 @@ module Rigor
|
|
|
1658
1672
|
# `false`/`nil` folds.
|
|
1659
1673
|
def invoke_ternary(receiver_value, method_name, av0, av1)
|
|
1660
1674
|
return nil unless ternary_method_allowed?(receiver_value, method_name)
|
|
1675
|
+
return OVERSIZED_STRING if string_ternary_blow_up?(receiver_value, method_name, av0, av1)
|
|
1661
1676
|
|
|
1662
1677
|
fold_result(receiver_value.public_send(method_name, av0, av1))
|
|
1663
1678
|
rescue StandardError
|
|
@@ -1667,7 +1682,7 @@ module Rigor
|
|
|
1667
1682
|
# Returns `[value]` on success, `nil` to signal "skip". See `invoke_binary` for why we wrap.
|
|
1668
1683
|
def invoke_unary(receiver_value, method_name)
|
|
1669
1684
|
return nil unless unary_safe?(receiver_value, method_name)
|
|
1670
|
-
return
|
|
1685
|
+
return OVERSIZED_STRING if string_unary_blow_up?(receiver_value, method_name)
|
|
1671
1686
|
return nil if range_enumeration_blow_up?(receiver_value, method_name)
|
|
1672
1687
|
|
|
1673
1688
|
fold_result(receiver_value.public_send(method_name))
|
|
@@ -1675,10 +1690,11 @@ module Rigor
|
|
|
1675
1690
|
nil
|
|
1676
1691
|
end
|
|
1677
1692
|
|
|
1678
|
-
# `[result]` for a foldable value, `[WidenedResult]` for a number past `INTEGER_FOLD_BIT_LIMIT`,
|
|
1679
|
-
# for anything else.
|
|
1693
|
+
# `[result]` for a foldable value, `[WidenedResult]` for a number past `INTEGER_FOLD_BIT_LIMIT`,
|
|
1694
|
+
# `OVERSIZED_STRING` for a String past `STRING_FOLD_BYTE_LIMIT`, nil for anything else.
|
|
1680
1695
|
def fold_result(result)
|
|
1681
1696
|
return [WidenedResult.new(result.class.name)] if oversized_number?(result)
|
|
1697
|
+
return OVERSIZED_STRING if oversized_string?(result)
|
|
1682
1698
|
|
|
1683
1699
|
foldable_constant_value?(result) ? [result] : nil
|
|
1684
1700
|
end
|
|
@@ -1822,10 +1838,20 @@ module Rigor
|
|
|
1822
1838
|
return false if value.is_a?(Float) && value.nan?
|
|
1823
1839
|
return false if value.is_a?(Complex) && complex_nan?(value)
|
|
1824
1840
|
return false if oversized_number?(value)
|
|
1841
|
+
return false if oversized_string?(value)
|
|
1825
1842
|
|
|
1826
1843
|
true
|
|
1827
1844
|
end
|
|
1828
1845
|
|
|
1846
|
+
# #2125 — the result gate every String-producing fold passes through, the String twin of
|
|
1847
|
+
# `oversized_number?`. The operand prechecks (`string_blow_up?`, `string_ternary_blow_up?`,
|
|
1848
|
+
# `format_blow_up?`) keep the costly cases from being built at all; this catches the rest after the
|
|
1849
|
+
# fact (`tr` into multibyte characters, `dump`, `Integer#to_s(2)`, whose receiver
|
|
1850
|
+
# `INTEGER_FOLD_BIT_LIMIT` already bounds, so its rendering is at most 13 KB).
|
|
1851
|
+
def oversized_string?(value)
|
|
1852
|
+
value.is_a?(String) && value.bytesize > STRING_FOLD_BYTE_LIMIT
|
|
1853
|
+
end
|
|
1854
|
+
|
|
1829
1855
|
# True when an Integer, or an Integer part of a Rational / Complex, is past `INTEGER_FOLD_BIT_LIMIT`.
|
|
1830
1856
|
def oversized_number?(value)
|
|
1831
1857
|
case value
|
|
@@ -1874,7 +1900,6 @@ module Rigor
|
|
|
1874
1900
|
catalog_allows?(receiver_value, method_name)
|
|
1875
1901
|
return false unless allowed
|
|
1876
1902
|
return false if integer_division_by_zero?(receiver_value, method_name, arg_value)
|
|
1877
|
-
return false if string_blow_up?(receiver_value, method_name, arg_value)
|
|
1878
1903
|
|
|
1879
1904
|
true
|
|
1880
1905
|
end
|
|
@@ -1960,16 +1985,91 @@ module Rigor
|
|
|
1960
1985
|
case method_name
|
|
1961
1986
|
when :+ then string_concat_blow_up?(receiver_value, arg_value)
|
|
1962
1987
|
when :* then string_repeat_blow_up?(receiver_value, arg_value)
|
|
1988
|
+
when :% then format_blow_up?(receiver_value, arg_value)
|
|
1963
1989
|
when :center, :ljust, :rjust then string_pad_blow_up?(arg_value)
|
|
1964
1990
|
else false
|
|
1965
1991
|
end
|
|
1966
1992
|
end
|
|
1967
1993
|
|
|
1994
|
+
# #2125 — the two-argument String folds that can grow past the input: the padded `center` / `ljust`
|
|
1995
|
+
# / `rjust` (width, pad) and `sub` / `gsub` (pattern, replacement).
|
|
1996
|
+
def string_ternary_blow_up?(receiver_value, method_name, av0, av1)
|
|
1997
|
+
return false unless receiver_value.is_a?(String)
|
|
1998
|
+
|
|
1999
|
+
case method_name
|
|
2000
|
+
when :center, :ljust, :rjust then string_pad_blow_up?(av0)
|
|
2001
|
+
when :sub, :gsub then string_substitution_blow_up?(receiver_value, method_name, av1)
|
|
2002
|
+
else false
|
|
2003
|
+
end
|
|
2004
|
+
end
|
|
2005
|
+
|
|
2006
|
+
# An upper bound on a `sub` / `gsub` result with a String replacement: `gsub` matches at most once per
|
|
2007
|
+
# position plus once at the end, and each replacement is the replacement's bytes plus, for every
|
|
2008
|
+
# backslash in it (`\0`, `\1`, `\k<name>`, `` \` ``, `\'`), at most the receiver's bytes again.
|
|
2009
|
+
def string_substitution_blow_up?(receiver_value, method_name, replacement)
|
|
2010
|
+
return false unless replacement.is_a?(String)
|
|
2011
|
+
|
|
2012
|
+
size = receiver_value.bytesize
|
|
2013
|
+
matches = method_name == :gsub ? size + 1 : 1
|
|
2014
|
+
per_match = replacement.bytesize + (replacement.count("\\") * size)
|
|
2015
|
+
size + (matches * per_match) > STRING_FOLD_BYTE_LIMIT
|
|
2016
|
+
end
|
|
2017
|
+
|
|
2018
|
+
# #2125 — `String#%` / `Kernel#format` pad to a field width and render to a precision the format
|
|
2019
|
+
# string spells (`"%02000000000d"`, `"%.100000f"`) or an argument supplies (`"%*d" % [n, 1]`).
|
|
2020
|
+
# Formatting builds the whole padded string before any result gate can look at it — two billion
|
|
2021
|
+
# bytes took 13 s and 4 GB — so the numbers are read from the directives first. A `*` width counts
|
|
2022
|
+
# every Integer argument as a possible width, and a `%%` escape matches as a whole, carrying no digits.
|
|
2023
|
+
FORMAT_DIRECTIVE = /%%|%(?:<[^>]*>|\{[^}]*\}|[-+ 0#*$.\d])*/
|
|
2024
|
+
private_constant :FORMAT_DIRECTIVE
|
|
2025
|
+
|
|
2026
|
+
def format_blow_up?(format_string, arguments)
|
|
2027
|
+
star = false
|
|
2028
|
+
format_string.scan(FORMAT_DIRECTIVE) do |directive|
|
|
2029
|
+
spec = directive.gsub(/<[^>]*>|\{[^}]*\}|\d+\$/, "")
|
|
2030
|
+
return true if spec.scan(/\d+/).any? { |digits| digits.to_i > STRING_FOLD_BYTE_LIMIT }
|
|
2031
|
+
|
|
2032
|
+
star ||= spec.include?("*")
|
|
2033
|
+
end
|
|
2034
|
+
star && format_argument_values(arguments).any? do |value|
|
|
2035
|
+
width = integer_operand(value)
|
|
2036
|
+
!width.nil? && width.abs > STRING_FOLD_BYTE_LIMIT
|
|
2037
|
+
end
|
|
2038
|
+
end
|
|
2039
|
+
|
|
2040
|
+
def format_argument_values(arguments)
|
|
2041
|
+
case arguments
|
|
2042
|
+
when Hash then arguments.values
|
|
2043
|
+
when Array then arguments
|
|
2044
|
+
else [arguments]
|
|
2045
|
+
end
|
|
2046
|
+
end
|
|
2047
|
+
|
|
1968
2048
|
# `"x".center(width)` / `#ljust` / `#rjust` produce a string of `max(width, len)` characters. A
|
|
1969
2049
|
# literal `width` far larger than the receiver would materialise a huge Constant; cap it at the same
|
|
1970
2050
|
# byte limit the concat / repeat paths use.
|
|
1971
2051
|
def string_pad_blow_up?(arg_value)
|
|
1972
|
-
|
|
2052
|
+
width = integer_operand(arg_value)
|
|
2053
|
+
!width.nil? && width > STRING_FOLD_BYTE_LIMIT
|
|
2054
|
+
end
|
|
2055
|
+
|
|
2056
|
+
# The Integer a width or count operand stands for. Ruby converts a Float or Rational one with `NUM2LONG`
|
|
2057
|
+
# (`"x" * 2.9` is `"xx"`, `"x".ljust(2e9)` pads to two billion), and a Complex one whose imaginary part is an
|
|
2058
|
+
# exact zero through its real part (`"x" * Complex(3, 0)` is `"xxx"`; `Complex(3, 0.0)` raises), so the
|
|
2059
|
+
# prechecks read it the same way. Those are every Numeric class a `Constant` carries. A non-finite Float
|
|
2060
|
+
# raises in Ruby, and any other operand is left to the call, so both answer nil.
|
|
2061
|
+
def integer_operand(value)
|
|
2062
|
+
case value
|
|
2063
|
+
when Integer then value
|
|
2064
|
+
when Float then value.finite? ? value.to_i : nil
|
|
2065
|
+
when Rational then value.to_i
|
|
2066
|
+
when Complex then exact_real_operand(value)
|
|
2067
|
+
end
|
|
2068
|
+
end
|
|
2069
|
+
|
|
2070
|
+
def exact_real_operand(value)
|
|
2071
|
+
imaginary = value.imaginary
|
|
2072
|
+
imaginary.is_a?(Integer) && imaginary.zero? ? integer_operand(value.real) : nil
|
|
1973
2073
|
end
|
|
1974
2074
|
|
|
1975
2075
|
def string_concat_blow_up?(receiver_value, arg_value)
|
|
@@ -1978,10 +2078,11 @@ module Rigor
|
|
|
1978
2078
|
end
|
|
1979
2079
|
|
|
1980
2080
|
def string_repeat_blow_up?(receiver_value, arg_value)
|
|
1981
|
-
|
|
1982
|
-
|
|
2081
|
+
# A negative count raises (`ArgumentError`), so the call itself skips that member, as any raising one is.
|
|
2082
|
+
count = integer_operand(arg_value)
|
|
2083
|
+
return false if count.nil? || count.negative?
|
|
1983
2084
|
|
|
1984
|
-
receiver_value.bytesize *
|
|
2085
|
+
receiver_value.bytesize * count > STRING_FOLD_BYTE_LIMIT
|
|
1985
2086
|
end
|
|
1986
2087
|
end
|
|
1987
2088
|
end
|