rigortype 0.3.8 → 0.4.0
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/capability_roles/capability_roles.rbs +36 -0
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +261 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +3 -3
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +112 -14
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +29 -11
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +16 -7
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +168 -21
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../source/node_children"
|
|
6
|
+
require_relative "../type"
|
|
7
|
+
require_relative "block_call_timing"
|
|
8
|
+
require_relative "fresh_frame_blocks"
|
|
9
|
+
require_relative "guard_rebinding"
|
|
10
|
+
require_relative "stored_block_call"
|
|
11
|
+
require_relative "match_rebinding/frame"
|
|
12
|
+
require_relative "match_rebinding/operands"
|
|
13
|
+
require_relative "match_rebinding/calls"
|
|
14
|
+
require_relative "match_rebinding/self_calls"
|
|
15
|
+
require_relative "last_line"
|
|
16
|
+
|
|
17
|
+
module Rigor
|
|
18
|
+
module Inference
|
|
19
|
+
# Which code may rebind the regex match globals (`$~` and the `$&` / `` $` `` / `$'` / `$+` / `$1`..`$9` family
|
|
20
|
+
# derived from it) that a scope's narrowing speaks for. Ruby keeps them in the method frame's special-variable
|
|
21
|
+
# slot, and every block and closure made in the method reaches that same slot, so a match a block body runs
|
|
22
|
+
# rebinds the enclosing method's `$~` (issue #1358). A `def`, class or module body runs in a frame of its own,
|
|
23
|
+
# and so does every call into a method defined in Ruby: a match in the callee writes the callee's slot, never
|
|
24
|
+
# its caller's (issue #1364). A thread, fiber or ractor keeps a slot of its own for the root block it runs
|
|
25
|
+
# ({FreshFrameBlocks}, issue #1361), so no scan here counts what that block runs; what it makes can still reach
|
|
26
|
+
# this frame's slot from the creator's thread, so the frame-wide rules keep reading inside it.
|
|
27
|
+
#
|
|
28
|
+
# The block and closure scans are syntactic, resolving only constants and the variables a lookup argument
|
|
29
|
+
# names, through the scope they are given. The code a statement runs outside a block is read call by call
|
|
30
|
+
# ({Calls}, {.value_may_rebind?}; issue #1365): a call the old name table forgot on keeps forgetting unless its
|
|
31
|
+
# literal arguments prove it match-free, and any other forgets only when it is known to match. The scans
|
|
32
|
+
# short-circuit without a `return` out of a child block, which would allocate once per frame it unwinds.
|
|
33
|
+
module MatchRebinding # rubocop:disable Metrics/ModuleLength
|
|
34
|
+
# The block and closure scans read calls on narrower terms than the statement-level rule ({Calls}), because
|
|
35
|
+
# inside a block `[]`, `split` and `index` are overwhelmingly lookups on hashes, arrays and strings whose key
|
|
36
|
+
# the scan cannot type, and counting them dropped the narrowing on correct code
|
|
37
|
+
# (`fields.each { |f| out[f] = row[f] }; $1.upcase`).
|
|
38
|
+
#
|
|
39
|
+
# These rebind `$~` whatever their argument: `sub` / `gsub` / `scan` with a String pattern still set it, and
|
|
40
|
+
# `!~` runs `=~`.
|
|
41
|
+
ALWAYS_MATCHING = Set[:=~, :!~, :match, :sub, :sub!, :gsub, :gsub!, :scan].freeze
|
|
42
|
+
# These rebind it only with a Regexp argument, so they count only when an argument is known to be one
|
|
43
|
+
# ({Operands.regexp_argument?}). `match?` never sets `$~`.
|
|
44
|
+
REGEXP_ARGUMENT = Set[
|
|
45
|
+
:[], :slice, :slice!, :index, :rindex, :partition, :rpartition, :split, :grep, :grep_v,
|
|
46
|
+
:start_with?, :byteindex, :byterindex, :any?, :all?, :none?, :one?
|
|
47
|
+
].freeze
|
|
48
|
+
# `grep` / `grep_v` rebind the caller's `$~` only in their block form.
|
|
49
|
+
BLOCK_FORM_ONLY = Set[:grep, :grep_v].freeze
|
|
50
|
+
# `&:name` block arguments that rebind `$~`: the method runs on the element in the caller's frame. `===` with
|
|
51
|
+
# an unknown operand may be a Regexp's. Compared as Strings, as {SelfCalls} explains.
|
|
52
|
+
MATCHING_SYMBOL_PROCS = (ALWAYS_MATCHING | Set[:===]).to_set(&:to_s).freeze
|
|
53
|
+
# The broad reading ({.broad_may_match?}) also counts these with any argument that may be a Regexp.
|
|
54
|
+
BROAD_ARGUMENT = (REGEXP_ARGUMENT | Set[:[]=]).freeze
|
|
55
|
+
# The names issue #1364 added to the block scan. The entry of a `tap` / `then` / `yield_self` block reads its
|
|
56
|
+
# body without them ({.block_entry}).
|
|
57
|
+
ADDED_NAMES = Set[:!~, :start_with?, :byteindex, :byterindex, :any?, :all?, :none?, :one?].freeze
|
|
58
|
+
# The calls on the method's own `&block` parameter that run it, which the broad reading counts.
|
|
59
|
+
BLOCK_INVOCATIONS = Set[:call, :yield, :[], :===].freeze
|
|
60
|
+
|
|
61
|
+
# A body that runs in a frame of its own, or never runs (`defined?` evaluates nothing): every walk of what a
|
|
62
|
+
# frame runs stops there, `$_`'s ({LastLine}) included.
|
|
63
|
+
OWN_FRAME_NODES = Set[
|
|
64
|
+
Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefinedNode
|
|
65
|
+
].freeze
|
|
66
|
+
# A bare regex condition (`if /re/`) matches against `$_`.
|
|
67
|
+
LAST_LINE_MATCHES = Set[Prism::MatchLastLineNode, Prism::InterpolatedMatchLastLineNode].freeze
|
|
68
|
+
GLOBAL_WRITES = Set[
|
|
69
|
+
Prism::GlobalVariableWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode,
|
|
70
|
+
Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableTargetNode
|
|
71
|
+
].freeze
|
|
72
|
+
private_constant :ALWAYS_MATCHING, :REGEXP_ARGUMENT, :BLOCK_FORM_ONLY, :MATCHING_SYMBOL_PROCS, :BROAD_ARGUMENT,
|
|
73
|
+
:ADDED_NAMES, :BLOCK_INVOCATIONS,
|
|
74
|
+
:LAST_LINE_MATCHES, :GLOBAL_WRITES
|
|
75
|
+
|
|
76
|
+
module_function
|
|
77
|
+
|
|
78
|
+
# True when running `node` may run a regex match in the frame it runs in, anywhere in `node` but a nested
|
|
79
|
+
# `def`, class or module body: a call {.call_matches?} counts; so does a `when` condition of a `case` with a
|
|
80
|
+
# subject, or an `in` / `=>` pattern value, that may be a Regexp ({Operands}); a bare regex condition; a
|
|
81
|
+
# write to `$~`; or a block argument {.block_argument_may_match?} counts. A block or lambda inside `node`
|
|
82
|
+
# counts, since it runs in the same frame. `scope` resolves constants and the variables a lookup argument
|
|
83
|
+
# names, and names the frame; the answer for a node is kept on that frame while those bindings stay the same.
|
|
84
|
+
def may_match?(node, scope = nil)
|
|
85
|
+
return false unless node.is_a?(Prism::Node)
|
|
86
|
+
|
|
87
|
+
remember(node, scope) { scan(node, scope) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The answer the block gives for `node`, kept on `scope`'s frame under `kind` ({Frame#memo}) when a body
|
|
91
|
+
# stamped one.
|
|
92
|
+
def remember(node, scope, kind = nil, &)
|
|
93
|
+
frame = scope&.match_frame
|
|
94
|
+
frame.nil? ? yield : frame.memo(node, scope, kind, &)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# `base` reads the body without {ADDED_NAMES}.
|
|
98
|
+
def scan(node, scope, base: false)
|
|
99
|
+
return true if matching_node?(node, scope, base: base)
|
|
100
|
+
return false if OWN_FRAME_NODES.include?(node.class)
|
|
101
|
+
|
|
102
|
+
FreshFrameBlocks.any_frame_child?(node, scope) { |child| scan(child, scope, base: base) }
|
|
103
|
+
end
|
|
104
|
+
private_class_method :scan
|
|
105
|
+
|
|
106
|
+
# `broad` reads an unresolved constant as a possible Regexp ({Operands}).
|
|
107
|
+
def matching_node?(node, scope, broad: false, base: false)
|
|
108
|
+
case node
|
|
109
|
+
when Prism::CallNode then call_matches?(node, scope, broad: broad, base: base)
|
|
110
|
+
when Prism::CaseNode then case_matches?(node, scope, broad: broad)
|
|
111
|
+
when Prism::BlockArgumentNode then block_argument_may_match?(node, scope, base: base)
|
|
112
|
+
# `case … in`, `expr in pat` and `expr => pat` run `===` on each value in the pattern.
|
|
113
|
+
when Prism::InNode, Prism::MatchPredicateNode, Prism::MatchRequiredNode
|
|
114
|
+
Operands.pattern_matches?(node.pattern, scope, broad: broad)
|
|
115
|
+
else
|
|
116
|
+
LAST_LINE_MATCHES.include?(node.class) || (GLOBAL_WRITES.include?(node.class) && node.name == :$~)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
private_class_method :matching_node?
|
|
120
|
+
|
|
121
|
+
# A call that rebinds `$~` in the frame it is made in: an {ALWAYS_MATCHING} name, a {REGEXP_ARGUMENT} name
|
|
122
|
+
# with a known Regexp argument, or `===` on a receiver that may be a Regexp — `a === b` is `a`'s method, so
|
|
123
|
+
# `String === re` runs no match.
|
|
124
|
+
def call_matches?(node, scope, broad: false, base: false)
|
|
125
|
+
name = node.name
|
|
126
|
+
return false if base && ADDED_NAMES.include?(name)
|
|
127
|
+
return true if ALWAYS_MATCHING.include?(name)
|
|
128
|
+
|
|
129
|
+
if name == :===
|
|
130
|
+
receiver = node.receiver
|
|
131
|
+
return receiver.nil? || Operands.pattern_value?(receiver, scope, broad: broad)
|
|
132
|
+
end
|
|
133
|
+
arguments = node.arguments&.arguments
|
|
134
|
+
return false unless arguments && REGEXP_ARGUMENT.include?(name)
|
|
135
|
+
return false if BLOCK_FORM_ONLY.include?(name) && node.block.nil?
|
|
136
|
+
|
|
137
|
+
arguments.any? { |argument| Operands.regexp_argument?(argument, scope) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# A `case` with a subject runs `condition === subject` for each `when` condition. One without a subject tests
|
|
141
|
+
# each condition for truth, which runs no `===`; a match inside a condition is scanned like any other code.
|
|
142
|
+
def case_matches?(node, scope, broad: false)
|
|
143
|
+
return false if node.predicate.nil?
|
|
144
|
+
|
|
145
|
+
node.conditions.any? do |clause|
|
|
146
|
+
clause.is_a?(Prism::WhenNode) &&
|
|
147
|
+
clause.conditions.any? { |condition| Operands.pattern_value?(condition, scope, broad: broad) }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
private_class_method :call_matches?, :case_matches?
|
|
151
|
+
|
|
152
|
+
# True when an implicit-self call's arguments may run a match in this frame (issue #1364), outside a block or
|
|
153
|
+
# lambda, which {.value_may_rebind?} and the frame's closure rules answer for: a call the table or
|
|
154
|
+
# {SelfCalls} names on any receiver, unless its syntax proves it cannot match ({Calls.forgets_by_name?}, issue
|
|
155
|
+
# #1365: `log(row[:name])` keeps the narrowing, `log(row[key])` does not); a Symbol or String literal naming
|
|
156
|
+
# one (`inject(:=~)`), which the implicit-self call may run from C; a `yield`; or any other construct the
|
|
157
|
+
# block scan counts, an unresolved constant read as a possible Regexp. An implicit-self call used to forget
|
|
158
|
+
# whatever it called, and this keeps it forgetting wherever it forgot then but on that proof; a call in an
|
|
159
|
+
# argument that is known to match forgets by itself too ({.operands_may_rebind?}).
|
|
160
|
+
def operand_may_match?(node, scope = nil)
|
|
161
|
+
return false unless node.is_a?(Prism::Node)
|
|
162
|
+
|
|
163
|
+
case node
|
|
164
|
+
when Prism::BlockNode, Prism::LambdaNode then return false
|
|
165
|
+
when Prism::CallNode
|
|
166
|
+
return true if Calls.base_named?(node, implicit: true) && Calls.forgets_by_name?(node, scope)
|
|
167
|
+
when Prism::YieldNode then return true
|
|
168
|
+
else
|
|
169
|
+
return true if SelfCalls.method_name_literal?(node) || matching_node?(node, scope, broad: true)
|
|
170
|
+
end
|
|
171
|
+
return false if OWN_FRAME_NODES.include?(node.class)
|
|
172
|
+
|
|
173
|
+
FreshFrameBlocks.any_frame_child?(node, scope) { |child| operand_may_match?(child, scope) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# True when `node`, a frame's body or parameters, hands its match globals to code the analyzer does not trace,
|
|
177
|
+
# so an implicit-self call in the frame keeps forgetting them as every one did before issue #1364: a block or
|
|
178
|
+
# lambda literal whose body may match by the broad reading ({.broad_may_match?}), since the call it is passed
|
|
179
|
+
# to may keep it and run it from any later call; `binding` in any spelling, whose `eval` runs in this frame
|
|
180
|
+
# wherever it is called; or a forward of the method's own block (`&blk` for the method's `&blk`, `&`, `...`),
|
|
181
|
+
# which may be a C-function proc such as `&:=~`. `block_name` is the method's `&block` parameter. The root block
|
|
182
|
+
# of a thread, fiber or ractor ({FreshFrameBlocks.root_block}) is not handed to such code: it runs with a slot
|
|
183
|
+
# of its own. A block, lambda or `binding` inside it is read as anywhere else, since it may be handed back to
|
|
184
|
+
# this frame's thread (`Thread.new { -> { s =~ re } }.value.call` rebinds the creator's `$~`).
|
|
185
|
+
def self_call_fallback?(node, block_name, scope = nil)
|
|
186
|
+
hands_out_slot?(node, block_name, scope) { |body| broad_may_match?(body, scope, block_name) }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# The walk under {.self_call_fallback?}, shared with `$_` ({LastLine.fallback?}): the block answers whether a
|
|
190
|
+
# block or lambda body may rebind the special it asks about.
|
|
191
|
+
def hands_out_slot?(node, block_name, scope, &)
|
|
192
|
+
return false unless node.is_a?(Prism::Node)
|
|
193
|
+
return true if handoff_node?(node, block_name, &)
|
|
194
|
+
return false if OWN_FRAME_NODES.include?(node.class)
|
|
195
|
+
|
|
196
|
+
FreshFrameBlocks.any_frame_child?(node, scope, into_root: true) do |child|
|
|
197
|
+
hands_out_slot?(child, block_name, scope, &)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def handoff_node?(node, block_name)
|
|
202
|
+
case node
|
|
203
|
+
when Prism::BlockNode, Prism::LambdaNode then yield(node.body)
|
|
204
|
+
when Prism::CallNode then node.name == :binding || SelfCalls.sends_binding?(node)
|
|
205
|
+
when Prism::ForwardingArgumentsNode then true
|
|
206
|
+
when Prism::BlockArgumentNode
|
|
207
|
+
expression = node.expression
|
|
208
|
+
expression.nil? || (expression.is_a?(Prism::LocalVariableReadNode) && expression.name == block_name)
|
|
209
|
+
else false
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# {.may_match?} on broad terms, where over-counting costs no more than an implicit-self call forgetting as it
|
|
214
|
+
# did before issue #1364: a {BROAD_ARGUMENT} name counts with any argument that may be a Regexp — a block
|
|
215
|
+
# parameter, a method's return value, a constant that does not resolve (#1373) — and so does a call {SelfCalls}
|
|
216
|
+
# reads by name, a literal naming one, a `yield`, or a call that runs the method's own `&block` (`block_name`),
|
|
217
|
+
# either of which may run a C-function proc such as `&:=~` in this frame.
|
|
218
|
+
def broad_may_match?(node, scope = nil, block_name = nil)
|
|
219
|
+
return false unless node.is_a?(Prism::Node)
|
|
220
|
+
return true if broad_matching_node?(node, scope, block_name)
|
|
221
|
+
return false if OWN_FRAME_NODES.include?(node.class)
|
|
222
|
+
|
|
223
|
+
FreshFrameBlocks.any_frame_child?(node, scope) { |child| broad_may_match?(child, scope, block_name) }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def broad_matching_node?(node, scope, block_name)
|
|
227
|
+
return true if node.is_a?(Prism::YieldNode)
|
|
228
|
+
unless node.is_a?(Prism::CallNode)
|
|
229
|
+
return matching_node?(node, scope, broad: true) || SelfCalls.method_name_literal?(node)
|
|
230
|
+
end
|
|
231
|
+
return true if call_matches?(node, scope, broad: true) || SelfCalls.named_match?(node)
|
|
232
|
+
return true if own_block_call?(node, block_name)
|
|
233
|
+
|
|
234
|
+
arguments = node.arguments&.arguments
|
|
235
|
+
!arguments.nil? && BROAD_ARGUMENT.include?(node.name) &&
|
|
236
|
+
arguments.any? { |argument| Operands.pattern_value?(argument, scope, broad: true) }
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def own_block_call?(call_node, block_name)
|
|
240
|
+
receiver = call_node.receiver
|
|
241
|
+
!block_name.nil? && receiver.is_a?(Prism::LocalVariableReadNode) && receiver.name == block_name &&
|
|
242
|
+
BLOCK_INVOCATIONS.include?(call_node.name)
|
|
243
|
+
end
|
|
244
|
+
private_class_method :handoff_node?, :broad_matching_node?, :own_block_call?
|
|
245
|
+
|
|
246
|
+
# True when the block `call_node` passes may rebind the frame's match globals while the call runs: a block
|
|
247
|
+
# literal whose body {.may_match?}, or a block argument that {.block_argument_may_match?}. The root block of a
|
|
248
|
+
# thread, fiber or ractor never does ({FreshFrameBlocks.root_call?}); a root `&expr` argument's expression runs
|
|
249
|
+
# here first, as an operand does ({.value_may_rebind?}).
|
|
250
|
+
def block_may_match?(call_node, scope = nil)
|
|
251
|
+
block = call_node.block
|
|
252
|
+
if FreshFrameBlocks.root_call?(call_node, scope)
|
|
253
|
+
return block.is_a?(Prism::BlockArgumentNode) && value_may_rebind?(block.expression, scope)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
case block
|
|
257
|
+
when Prism::BlockNode then may_match?(block.body, scope)
|
|
258
|
+
when Prism::BlockArgumentNode then block_argument_may_match?(block, scope)
|
|
259
|
+
else false
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# True when a `&expr` block argument may pass a proc made in this frame. Not an anonymous `&`, nor the method's
|
|
264
|
+
# own `&block` parameter while the body never rebinds or shadows it ({Frame#forwarded_block?}): either
|
|
265
|
+
# forwards the block the caller made, in the caller's frame. Not a `&:name` whose method cannot match
|
|
266
|
+
# ({MATCHING_SYMBOL_PROCS}).
|
|
267
|
+
def block_argument_may_match?(block_argument, scope, base: false)
|
|
268
|
+
expression = block_argument.expression
|
|
269
|
+
case expression
|
|
270
|
+
when nil then false
|
|
271
|
+
when Prism::SymbolNode
|
|
272
|
+
name = expression.unescaped
|
|
273
|
+
MATCHING_SYMBOL_PROCS.include?(name) && !(base && name == "!~")
|
|
274
|
+
when Prism::LocalVariableReadNode
|
|
275
|
+
frame = scope&.match_frame
|
|
276
|
+
frame.nil? || !frame.forwarded_block?(expression.name)
|
|
277
|
+
else true
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# True when `program`, a String of code an eval runs in this frame, may match, on the block scan's terms: its
|
|
282
|
+
# statements run here, and a `def` or class in it runs in a frame of its own. The caller keeps the answer.
|
|
283
|
+
def program_may_match?(program, scope) = scan(program, scope)
|
|
284
|
+
|
|
285
|
+
# True when running a call's receiver chain and arguments, which Ruby does before the method, may rebind the
|
|
286
|
+
# frame's match globals ({.value_may_rebind?}), kept on the frame as {Frame#memo} keeps a scan.
|
|
287
|
+
def operands_may_rebind?(call_node, scope)
|
|
288
|
+
remember(call_node, scope, :operands) do
|
|
289
|
+
value_may_rebind?(call_node.receiver, scope) || value_may_rebind?(call_node.arguments, scope)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# True when evaluating `node` for its value — a call's receiver chain or arguments, an array, hash or
|
|
294
|
+
# interpolation literal, a `rescue` modifier, a `super` or `yield` — may rebind the frame's match globals
|
|
295
|
+
# (issue #1365). No call there forgot before, so each forgets only when it is known to match
|
|
296
|
+
# ({Calls.rebinds?}): `[u.index(/(q)/)]` does, `out << row[key]` and `x = [h[k], 1]` do not. So does an index
|
|
297
|
+
# write whose index is known to be a Regexp (`s[re] ||= v`), a block literal on a call there whose body
|
|
298
|
+
# {.may_match?}, a block argument {.block_argument_may_match?} counts, or any other construct the block scan
|
|
299
|
+
# counts (a `when` or `in` value that may be a Regexp, a bare regex condition, a write to `$~`). An
|
|
300
|
+
# implicit-self call there is read the same way: the frame-wide fallback of {Calls.statement_rebinds?} stays with
|
|
301
|
+
# statement-position calls, as before, where an operand's `value.upcase` would otherwise forget at every
|
|
302
|
+
# attribute read. A lambda literal does not run where it is written ({.matching_closure?} answers for it),
|
|
303
|
+
# and a `def`, class or module body or a `defined?` operand does not run in this frame.
|
|
304
|
+
def value_may_rebind?(node, scope)
|
|
305
|
+
return false unless node.is_a?(Prism::Node)
|
|
306
|
+
|
|
307
|
+
case node
|
|
308
|
+
when Prism::BlockNode then return may_match?(node.body, scope)
|
|
309
|
+
when Prism::LambdaNode then return false
|
|
310
|
+
when Prism::CallNode, Prism::IndexOrWriteNode, Prism::IndexAndWriteNode, Prism::IndexOperatorWriteNode
|
|
311
|
+
return true if Calls.rebinds?(node, scope)
|
|
312
|
+
else return true if matching_node?(node, scope)
|
|
313
|
+
end
|
|
314
|
+
return false if OWN_FRAME_NODES.include?(node.class)
|
|
315
|
+
|
|
316
|
+
FreshFrameBlocks.any_frame_child?(node, scope) { |child| value_may_rebind?(child, scope) }
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# True when `node`, a frame's body or parameters, makes a closure that may rebind the frame's match globals
|
|
320
|
+
# whenever it is invoked: a `->` literal, or the block of a call that keeps it to run later
|
|
321
|
+
# ({StoredBlockCall}: `lambda`, `proc`, `Proc.new`, `define_method`, …), whose body {.may_match?}.
|
|
322
|
+
# Invocations are not traced — the closure can be called through any later call, or run by a method it was
|
|
323
|
+
# handed to — so {Frame} answers for the whole frame. The root block of a thread or fiber is kept too, but it
|
|
324
|
+
# only ever runs as that thread's or fiber's root, with a slot of its own ({FreshFrameBlocks}); a closure made
|
|
325
|
+
# inside it still counts, since it may be handed back to this frame's thread.
|
|
326
|
+
def matching_closure?(node, scope = nil) = makes_closure?(node, scope) { |body| may_match?(body, scope) }
|
|
327
|
+
|
|
328
|
+
# The walk under {.matching_closure?}, shared with `$_` ({LastLine.closure?}): the block answers whether a
|
|
329
|
+
# closure body may rebind the special it asks about.
|
|
330
|
+
def makes_closure?(node, scope, &)
|
|
331
|
+
return false unless node.is_a?(Prism::Node)
|
|
332
|
+
return false if OWN_FRAME_NODES.include?(node.class)
|
|
333
|
+
return true if node.is_a?(Prism::LambdaNode) && yield(node.body)
|
|
334
|
+
return true if node.is_a?(Prism::CallNode) && stored_block_rebinds?(node, scope, &)
|
|
335
|
+
|
|
336
|
+
found = false
|
|
337
|
+
node.rigor_each_child { |child| found ||= makes_closure?(child, scope, &) }
|
|
338
|
+
found
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def stored_block_rebinds?(call_node, scope)
|
|
342
|
+
block = call_node.block
|
|
343
|
+
block.is_a?(Prism::BlockNode) && StoredBlockCall.stores_block?(call_node) &&
|
|
344
|
+
!FreshFrameBlocks.root_call?(call_node, scope) && yield(block.body)
|
|
345
|
+
end
|
|
346
|
+
private_class_method :stored_block_rebinds?
|
|
347
|
+
|
|
348
|
+
# The scope a block or lambda body enters with: `scope` with its match globals forgotten when the body may
|
|
349
|
+
# match, or when the frame makes a closure that may. The body can run on a later iteration, after an earlier
|
|
350
|
+
# one — or a call to that closure — rebound them, so no iteration may read the narrowing the call site holds.
|
|
351
|
+
# A body with neither keeps it: blocks share the frame, so `s =~ /(\d+)/; items.map { $1 }` reads the
|
|
352
|
+
# guard's `$1`. The block of a call named `tap`, `then` or `yield_self` ({BlockCallTiming}) is read as the scan
|
|
353
|
+
# read every block before issue #1364, without {ADDED_NAMES}, so its entry is what it was: the name alone
|
|
354
|
+
# cannot show the block runs once (a user `then` may keep it, and a loop runs the call again, #1375). With
|
|
355
|
+
# the owning `call_node`, the entry also forgets when the call's receiver chain or arguments may rebind them
|
|
356
|
+
# ({.operands_may_rebind?}, issue #1365): Ruby runs those before the method yields, so
|
|
357
|
+
# `[u.index(/(q)/)].map { $1 }` reads the rebound `$1`. Every block-entry pass enters through here:
|
|
358
|
+
# {StatementEvaluator#build_block_entry_scope} and the block-return and `break` passes in {ExpressionTyper}
|
|
359
|
+
# pass the call. The per-element fold and the captured-local fixpoint pass none; they run under
|
|
360
|
+
# `ExpressionTyper#rebound_operand_typer`, whose scope has already forgotten the globals when the operands
|
|
361
|
+
# may rebind them, so no pass disagrees. Neither runs for the block of a call {FreshFrameBlocks} names, which is
|
|
362
|
+
# no iterator's; that block enters as {FreshFrameBlocks.entry} gives (issue #1361): a thread's, fiber's or
|
|
363
|
+
# ractor's root block with the globals unbound, since it reads a slot of its own, and a `define_method` body
|
|
364
|
+
# with a narrowed global untyped, since it reads the definer's slot whenever the method is called. `$_`, which
|
|
365
|
+
# shares the slot, enters on the same terms ({LastLine.block_entry}, issue #1359). A closure's body, which runs
|
|
366
|
+
# whenever it is called, enters with `$!`, `$@` and `$?` unbound ({FreshFrameBlocks.closure_entry}, issue #1360).
|
|
367
|
+
#
|
|
368
|
+
# Issue #1429 — the entry also restores a guard's narrowing of a global or constant where the body may run after
|
|
369
|
+
# code that rebinds it ({GuardRebinding.block_entry}).
|
|
370
|
+
def block_entry(scope, block_node, call_node = nil)
|
|
371
|
+
scope = GuardRebinding.block_entry(scope, block_node, call_node)
|
|
372
|
+
return FreshFrameBlocks.entry(scope, call_node) if FreshFrameBlocks.fresh_entry?(call_node, scope)
|
|
373
|
+
|
|
374
|
+
scope = FreshFrameBlocks.closure_entry(scope, block_node, call_node)
|
|
375
|
+
scope = LastLine.block_entry(scope, block_node, call_node)
|
|
376
|
+
return scope unless scope.match_globals_bound?
|
|
377
|
+
return scope unless scope.match_rebinding_closure? || entry_may_match?(block_node.body, scope, call_node) ||
|
|
378
|
+
(call_node.is_a?(Prism::CallNode) && operands_may_rebind?(call_node, scope))
|
|
379
|
+
|
|
380
|
+
scope.forget_match_globals
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def entry_may_match?(body, scope, call_node)
|
|
384
|
+
return may_match?(body, scope) unless call_node.is_a?(Prism::CallNode) &&
|
|
385
|
+
BlockCallTiming.candidate_name?(call_node.name)
|
|
386
|
+
|
|
387
|
+
body.is_a?(Prism::Node) && scan(body, scope, base: true)
|
|
388
|
+
end
|
|
389
|
+
private_class_method :entry_may_match?
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
module MethodDispatcher
|
|
6
|
+
module OverloadSelector
|
|
7
|
+
# Canonical RBS-core aliases shipped by `core/builtin.rbs` whose body is `<Nominal> | _DuckType`.
|
|
8
|
+
# Matching an overload against an Integer literal should pick the `(int) -> Array[Elem]` body over
|
|
9
|
+
# the `(string) -> String` body because Integer satisfies `int`'s strict arm and not `string`'s.
|
|
10
|
+
# The translator collapses both aliases to `Dynamic[Top]` (interfaces are not structurally matched
|
|
11
|
+
# yet), so a dedicated pass 1.5 between strict and gradual consults this map to pick the alias
|
|
12
|
+
# whose strict arm matches.
|
|
13
|
+
#
|
|
14
|
+
# Symbol keys are the alias names as they appear under `RBS::Types::Alias#name.to_s` (the `name` is
|
|
15
|
+
# a `TypeName` whose `to_s` includes the `::` prefix). Values are an Array of class names whose
|
|
16
|
+
# Nominal[..] form is the alias's strict-arm matcher.
|
|
17
|
+
#
|
|
18
|
+
# `range[T] = Range[T] | _Range[T]` is generic, unlike the others, but its strict arm is still a single
|
|
19
|
+
# nominal and the args are irrelevant to this pass. rbs 4.1 rewrote `Array#[]`'s slicing overload from
|
|
20
|
+
# `(::Range[::Integer?])` to `(range[int])`; without the entry both it and the `(int) -> E` overload look
|
|
21
|
+
# alias-typed, so `a[1..2]` resolved to the element type.
|
|
22
|
+
ALIAS_STRICT_NOMINALS = Ractor.make_shareable({
|
|
23
|
+
"::int" => ["Integer"],
|
|
24
|
+
"::string" => ["String"],
|
|
25
|
+
"::interned" => %w[Symbol String],
|
|
26
|
+
"::io" => ["IO"],
|
|
27
|
+
"::encoding" => %w[Encoding String],
|
|
28
|
+
"::path" => ["String"],
|
|
29
|
+
"::boolean" => %w[TrueClass FalseClass],
|
|
30
|
+
"::range" => ["Range"]
|
|
31
|
+
})
|
|
32
|
+
private_constant :ALIAS_STRICT_NOMINALS
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "iterator_dispatch"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -15,13 +16,20 @@ module Rigor
|
|
|
15
16
|
# The methods covered fall in two families:
|
|
16
17
|
#
|
|
17
18
|
# - **Filter-shaped** (`select` / `filter` / `reject` / `take_while` / `drop_while`): the block's
|
|
18
|
-
# truthiness selects the all-or-nothing endpoints — either
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
19
|
+
# truthiness selects the all-or-nothing endpoints — either every element kept or every element
|
|
20
|
+
# dropped — and each endpoint is a value of the class the method returns. `Hash#select` / `#filter`
|
|
21
|
+
# / `#reject` keep the Hash receiver or drop to the empty HashShape; every other combination is
|
|
22
|
+
# Enumerable's and returns an Array, so it drops to the empty-tuple carrier and keeps the receiver
|
|
23
|
+
# only when that receiver is itself an Array (otherwise the all-kept side declines).
|
|
24
|
+
# - **Predicate-shaped** (`all?` / `any?` / `none?`, without a pattern argument): the block's
|
|
25
|
+
# truthiness combined with the receiver's emptiness collapses the call to a `Constant[bool]` in the
|
|
26
|
+
# cases where Ruby's actual semantics make it unconditional. Non-empty + truthy `any?` is `true`;
|
|
27
|
+
# non-empty + falsey `all?` is `false`; the empty-receiver "vacuous" answers (`[].all? { false } ==
|
|
28
|
+
# true`, `[].any? { true } == false`, `[].none? { true } == true`) are likewise honoured.
|
|
29
|
+
#
|
|
30
|
+
# One family reads no block truthiness at all: **edge-selecting** `min_by` / `max_by` without a count
|
|
31
|
+
# answer `nil` only for an empty receiver, so on a receiver whose non-emptiness is a static fact they
|
|
32
|
+
# answer the element type without the `nil` the RBS `Elem?` return adds ({#fold_edge_by}).
|
|
25
33
|
#
|
|
26
34
|
# The dispatcher returns `nil` for any case that cannot be decided from the (receiver-shape, method,
|
|
27
35
|
# block-truthiness) tuple — element-wise block re-evaluation against `Constant<Array>` receivers (the
|
|
@@ -32,6 +40,16 @@ module Rigor
|
|
|
32
40
|
FILTER_KEEP_ON_TRUTHY = Set[:select, :filter, :take_while].freeze
|
|
33
41
|
FILTER_KEEP_ON_FALSEY = Set[:reject, :drop_while].freeze
|
|
34
42
|
|
|
43
|
+
# The filter methods `Hash` defines itself, returning a Hash. `take_while` / `drop_while` on a Hash
|
|
44
|
+
# are Enumerable's and return an Array of `[key, value]` pairs; `Set` and `Range` define none of the
|
|
45
|
+
# five, so all of theirs return an Array too.
|
|
46
|
+
HASH_RETURNING_FILTERS = Set[:select, :filter, :reject].freeze
|
|
47
|
+
|
|
48
|
+
# `filter_receiver_kind`'s answer for a nominal receiver; an unlisted class declines.
|
|
49
|
+
FILTER_NOMINAL_KINDS = {
|
|
50
|
+
"Array" => :array, "Hash" => :hash, "Set" => :enumerable, "Range" => :enumerable
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
35
53
|
PREDICATE_METHODS = Set[:all?, :any?, :none?].freeze
|
|
36
54
|
|
|
37
55
|
# Methods whose answer is `nil` when the block always returns Ruby-falsey — `find` / `detect`
|
|
@@ -46,25 +64,25 @@ module Rigor
|
|
|
46
64
|
# a finitely-sized receiver it is `Constant[size]`.
|
|
47
65
|
COUNT_METHOD = :count
|
|
48
66
|
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# @return [Rigor::Type, nil]
|
|
67
|
+
# `min_by` / `max_by` pick one element by the block's key, so the block's value decides WHICH element
|
|
68
|
+
# but never WHETHER one is returned: only an empty receiver answers `nil`.
|
|
69
|
+
EDGE_BY_METHODS = Set[:min_by, :max_by].freeze
|
|
70
|
+
|
|
71
|
+
# `context.block_type` is the inferred return type of the call's block; `nil` means "no block at the
|
|
72
|
+
# call site" and disqualifies every rule here.
|
|
56
73
|
def try_dispatch(context)
|
|
57
74
|
receiver = context.receiver
|
|
58
75
|
method_name = context.method_name
|
|
59
76
|
args = context.args
|
|
60
77
|
block_type = context.block_type
|
|
61
78
|
return nil if receiver.nil? || block_type.nil?
|
|
79
|
+
return fold_edge_by(receiver, args) if EDGE_BY_METHODS.include?(method_name)
|
|
62
80
|
|
|
63
81
|
truthiness = constant_truthiness(block_type)
|
|
64
82
|
return nil if truthiness.nil?
|
|
65
83
|
|
|
66
84
|
if PREDICATE_METHODS.include?(method_name)
|
|
67
|
-
fold_predicate(receiver, method_name, truthiness)
|
|
85
|
+
fold_predicate(receiver, method_name, truthiness, args)
|
|
68
86
|
elsif filter_method?(method_name)
|
|
69
87
|
fold_filter(receiver, method_name, truthiness)
|
|
70
88
|
elsif FALSEY_BLOCK_NIL_METHODS.include?(method_name)
|
|
@@ -74,6 +92,22 @@ module Rigor
|
|
|
74
92
|
end
|
|
75
93
|
end
|
|
76
94
|
|
|
95
|
+
# Issue #1333 — `min_by` / `max_by` with a block and no count on a non-empty Tuple or a non-empty
|
|
96
|
+
# constant integer `Range` answer the receiver's element type: the RBS `Elem?` return adds `nil` for
|
|
97
|
+
# the empty receiver, and `[1, 2].min_by { |s| rand(3) } + 1` then reported a nil receiver on correct
|
|
98
|
+
# code. The block's value picks which element, and one `block_type` typed from the call's entry scope
|
|
99
|
+
# cannot say which, so the answer is the whole element union. An empty receiver, one whose size is
|
|
100
|
+
# not static (`Array[T]`), and the count form (`min_by(2) { … }`, an Array) decline to RBS.
|
|
101
|
+
def fold_edge_by(receiver, args)
|
|
102
|
+
return nil unless args.empty?
|
|
103
|
+
return nil unless receiver_emptiness(receiver) == :non_empty
|
|
104
|
+
|
|
105
|
+
case receiver
|
|
106
|
+
when Type::Tuple then Type::Combinator.union(*receiver.elements)
|
|
107
|
+
when Type::Constant then IteratorDispatch.element_type_of(receiver) if receiver.value.is_a?(Range)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
77
111
|
def filter_method?(method_name)
|
|
78
112
|
FILTER_KEEP_ON_TRUTHY.include?(method_name) ||
|
|
79
113
|
FILTER_KEEP_ON_FALSEY.include?(method_name)
|
|
@@ -90,15 +124,28 @@ module Rigor
|
|
|
90
124
|
block_type.value ? :truthy : :falsey
|
|
91
125
|
end
|
|
92
126
|
|
|
93
|
-
# Filter-shaped methods collapse to either the receiver (every element kept) or the empty
|
|
94
|
-
# (every element dropped)
|
|
95
|
-
#
|
|
96
|
-
#
|
|
127
|
+
# Filter-shaped methods collapse to either the receiver (every element kept) or the empty collection
|
|
128
|
+
# (every element dropped), and the collection's class is the method's result class, not the
|
|
129
|
+
# receiver's: `{ a: 1 }.reject { true }` is `{}`, `{ a: 1 }.take_while { false }` is `[]`.
|
|
130
|
+
#
|
|
131
|
+
# The all-kept side answers the receiver only when the result class matches it — an Array for an
|
|
132
|
+
# Array receiver, a Hash for a Hash receiver's own filters. Otherwise (`Set[1].select { true }` is
|
|
133
|
+
# `[1]`, `{ a: 1 }.take_while { true }` is `[[:a, 1]]`) it declines so the RBS tier's
|
|
134
|
+
# `Array[Elem]` projection answers. Tuple-shaped receivers widen to `Array[union of elements]`
|
|
135
|
+
# on the all-kept side because we cannot prove WHICH positional subset survives — Tuple's
|
|
136
|
+
# per-position semantics do not carry over to a filtered Array.
|
|
97
137
|
def fold_filter(receiver, method_name, truthiness)
|
|
98
|
-
|
|
138
|
+
kind = filter_receiver_kind(receiver)
|
|
139
|
+
return nil if kind.nil?
|
|
99
140
|
|
|
100
|
-
|
|
101
|
-
|
|
141
|
+
hash_result = kind == :hash && HASH_RETURNING_FILTERS.include?(method_name)
|
|
142
|
+
unless filter_keeps_all?(method_name, truthiness)
|
|
143
|
+
return hash_result ? Type::Combinator.hash_shape_of({}) : Type::Combinator.tuple_of
|
|
144
|
+
end
|
|
145
|
+
return receiver if hash_result
|
|
146
|
+
return receiver_as_kept_array(receiver) if kind == :array
|
|
147
|
+
|
|
148
|
+
nil
|
|
102
149
|
end
|
|
103
150
|
|
|
104
151
|
def filter_keeps_all?(method_name, truthiness)
|
|
@@ -122,8 +169,11 @@ module Rigor
|
|
|
122
169
|
end
|
|
123
170
|
|
|
124
171
|
# Predicate folds. The decision table mirrors Ruby's actual semantics on `Enumerable#all?` / `#any?` /
|
|
125
|
-
# `#none?` — see the table at the top of the module.
|
|
126
|
-
|
|
172
|
+
# `#none?` — see the table at the top of the module. A pattern argument declines: Ruby then tests
|
|
173
|
+
# `pattern === element` and ignores the block, so `[1, 2].all?(String) { true }` is `false`.
|
|
174
|
+
def fold_predicate(receiver, method_name, truthiness, args)
|
|
175
|
+
return nil unless args.empty?
|
|
176
|
+
|
|
127
177
|
emptiness = receiver_emptiness(receiver)
|
|
128
178
|
decision = predicate_decision(method_name, truthiness, emptiness)
|
|
129
179
|
return nil if decision.nil?
|
|
@@ -135,7 +185,6 @@ module Rigor
|
|
|
135
185
|
end
|
|
136
186
|
end
|
|
137
187
|
|
|
138
|
-
# @return [:always_true, :always_false, :bool, nil]
|
|
139
188
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
140
189
|
def predicate_decision(method_name, truthiness, emptiness)
|
|
141
190
|
case method_name
|
|
@@ -167,7 +216,6 @@ module Rigor
|
|
|
167
216
|
)
|
|
168
217
|
end
|
|
169
218
|
|
|
170
|
-
# @return [:empty, :non_empty, :unknown]
|
|
171
219
|
def receiver_emptiness(receiver)
|
|
172
220
|
case receiver
|
|
173
221
|
when Type::Tuple
|
|
@@ -223,14 +271,20 @@ module Rigor
|
|
|
223
271
|
type.is_a?(Type::Nominal) && %w[Array Hash Set].include?(type.class_name)
|
|
224
272
|
end
|
|
225
273
|
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
|
|
274
|
+
# Classifies a filter receiver as `:array`, `:hash`, or `:enumerable` (a `Set` or `Range`, whose
|
|
275
|
+
# filters are all Enumerable's). Filter folds need at least a recognised collection carrier, so
|
|
276
|
+
# `Top` / `Dynamic` / arbitrary nominals / non-collection constants answer `nil` and decline so the
|
|
277
|
+
# RBS tier answers (its `Array#select { … } -> Array[T]` projection is correct, just less precise
|
|
278
|
+
# on the empty endpoint). A `Difference` (`non-empty-array[T]`, `non-empty-hash[K, V]`) classifies
|
|
279
|
+
# by its base. The `Constant` receivers are the ones `constant_emptiness` reads: `Range` literals
|
|
280
|
+
# and folded `Set` values.
|
|
281
|
+
def filter_receiver_kind(receiver)
|
|
230
282
|
case receiver
|
|
231
|
-
when Type::Tuple
|
|
232
|
-
when Type::
|
|
233
|
-
|
|
283
|
+
when Type::Tuple then :array
|
|
284
|
+
when Type::HashShape then :hash
|
|
285
|
+
when Type::Constant then :enumerable if receiver.value.is_a?(Range) || receiver.value.is_a?(::Set)
|
|
286
|
+
when Type::Difference then filter_receiver_kind(receiver.base)
|
|
287
|
+
when Type::Nominal then FILTER_NOMINAL_KINDS[receiver.class_name]
|
|
234
288
|
end
|
|
235
289
|
end
|
|
236
290
|
|