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,338 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
require_relative "../reflection"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
# Issue #1095 — the first slice of the block call-timing summary that
|
|
9
|
+
# `docs/type-specification/control-flow-analysis.md` § "Block call timing" asks for: which callees invoke
|
|
10
|
+
# their block **immediately, exactly once, and before they return**.
|
|
11
|
+
#
|
|
12
|
+
# The fact matters for the value of the call. Since #853 a block-level `break` joins the yielding call's
|
|
13
|
+
# type, unioned with what the callee returns when the block completes normally. For an arbitrary callee
|
|
14
|
+
# that normal return must stay — `each` may never yield on an empty receiver. For a callee that always
|
|
15
|
+
# yields exactly once before returning, a block whose normal completion is unreachable means the normal
|
|
16
|
+
# return is unreachable too, so the call's value is its `break` arms alone (`bot` when there are none):
|
|
17
|
+
# `[1, 2].tap { break "s" }` is `"s"`, `[1, 2].tap { raise "x" }` is `bot`.
|
|
18
|
+
#
|
|
19
|
+
# ## Catalogue
|
|
20
|
+
#
|
|
21
|
+
# Like {ClosureEscapeAnalyzer}'s `OBJECT_NON_ESCAPING` — a weaker "block is not retained" fact that `each`
|
|
22
|
+
# and `map` also satisfy — this ships as a hardcoded table keyed by `(owner, method)`, where the owner is
|
|
23
|
+
# the module whose declaration Ruby dispatches to. The sub-phase that replaces the escape catalogue with an
|
|
24
|
+
# `RBS::Extended` call-timing effect (`rbs-extended.md` reserves the bundle slot) is expected to absorb
|
|
25
|
+
# this table in the same move, which is why it is keyed by the declaring owner, the way a signature-borne
|
|
26
|
+
# effect would be, rather than by the receiver's class.
|
|
27
|
+
#
|
|
28
|
+
# The owner is `Kernel` for all three, in CRuby (`Object.instance_method(:tap).owner`) and in the bundled
|
|
29
|
+
# core RBS alike. `Object` is deliberately NOT accepted: every class inherits these methods through
|
|
30
|
+
# `Object`, so a declaration that resolves to `Object` itself is a project or gem override written on top
|
|
31
|
+
# of Kernel's, and that override is not known to yield exactly once.
|
|
32
|
+
#
|
|
33
|
+
# The summary is a pure query. It never raises on unrecognised input and answers `false` — keep the
|
|
34
|
+
# `break | normal-return` union — whenever it cannot prove the call reaches the catalogued declaration.
|
|
35
|
+
module BlockCallTiming
|
|
36
|
+
EXACTLY_ONCE_IMMEDIATE = {
|
|
37
|
+
"Kernel" => %i[tap then yield_self].freeze
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
CANDIDATE_NAMES = EXACTLY_ONCE_IMMEDIATE.values.flatten.uniq.freeze
|
|
41
|
+
private_constant :CANDIDATE_NAMES
|
|
42
|
+
|
|
43
|
+
# The roots a project monkey-patch would redefine the method on for every receiver at once.
|
|
44
|
+
PATCHABLE_ROOTS = %w[Object Kernel BasicObject].freeze
|
|
45
|
+
private_constant :PATCHABLE_ROOTS
|
|
46
|
+
|
|
47
|
+
# A class object's own metaclass ancestry adds these ahead of `Object`, so a patch on either one
|
|
48
|
+
# redefines the method for every class-object receiver.
|
|
49
|
+
SINGLETON_ROOTS = %w[Class Module].freeze
|
|
50
|
+
private_constant :SINGLETON_ROOTS
|
|
51
|
+
|
|
52
|
+
# Receiver-less (or `self.` / `Kernel.`) calls that never return normally: they raise, throw, or end
|
|
53
|
+
# the process. Anything else — including `loop`, whose RBS return is `bot` although a `StopIteration`
|
|
54
|
+
# ends it normally ({.loop_may_complete?}) — proves nothing.
|
|
55
|
+
NON_RETURNING_CALLS = %i[raise fail throw exit exit! abort].freeze
|
|
56
|
+
private_constant :NON_RETURNING_CALLS
|
|
57
|
+
|
|
58
|
+
# Issue #1107 — the nodes a `loop` body may consist of while still provably unable to raise
|
|
59
|
+
# `StopIteration`: none of them dispatches a method, so nothing in the body can run code that raises.
|
|
60
|
+
# Everything else — every call (operators and `[]` included), `yield`, `super`, interpolation, a splat,
|
|
61
|
+
# a constant read (`const_missing`), a `rescue` — may, and declines.
|
|
62
|
+
STOP_ITERATION_FREE_NODES = [
|
|
63
|
+
Prism::StatementsNode, Prism::ParenthesesNode, Prism::ArgumentsNode,
|
|
64
|
+
Prism::BreakNode, Prism::NextNode, Prism::RedoNode, Prism::ReturnNode,
|
|
65
|
+
Prism::IntegerNode, Prism::FloatNode, Prism::RationalNode, Prism::ImaginaryNode,
|
|
66
|
+
Prism::StringNode, Prism::SymbolNode, Prism::NilNode, Prism::TrueNode, Prism::FalseNode, Prism::SelfNode,
|
|
67
|
+
Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode,
|
|
68
|
+
Prism::InstanceVariableReadNode, Prism::InstanceVariableWriteNode,
|
|
69
|
+
Prism::IfNode, Prism::UnlessNode, Prism::ElseNode, Prism::AndNode, Prism::OrNode, Prism::ArrayNode
|
|
70
|
+
].freeze
|
|
71
|
+
private_constant :STOP_ITERATION_FREE_NODES
|
|
72
|
+
|
|
73
|
+
module_function
|
|
74
|
+
|
|
75
|
+
# Issue #1107 — whether a `loop` call can complete normally although its declared return is `bot`.
|
|
76
|
+
#
|
|
77
|
+
# `Kernel#loop` is `() { () -> void } -> bot` in core RBS, yet it rescues a `StopIteration` its block
|
|
78
|
+
# raises and returns that exception's `result` — the enumerator-draining idiom `loop { out << e.next }`
|
|
79
|
+
# returns `e`'s own `each` value. So the declared `bot` holds only for a body that provably cannot raise
|
|
80
|
+
# `StopIteration`: one built from {STOP_ITERATION_FREE_NODES} alone (`loop {}`, `loop { break 1 }`), in a
|
|
81
|
+
# block that declares no parameters. A `&blk` block-pass has no body to prove anything about and completes
|
|
82
|
+
# too.
|
|
83
|
+
#
|
|
84
|
+
# Gated on the spellings that reach the private `Kernel#loop` — receiver-less, `self.`, `Kernel.`,
|
|
85
|
+
# `::Kernel.` — so `obj.loop { ... }` is some other method, whose declared `bot` is that author's
|
|
86
|
+
# promise. A project redefinition of `loop` itself is not excluded: widening its `bot` costs precision,
|
|
87
|
+
# never a false positive.
|
|
88
|
+
def loop_may_complete?(call_node)
|
|
89
|
+
return false unless call_node.name == :loop && kernel_spelled_receiver?(call_node.receiver)
|
|
90
|
+
|
|
91
|
+
block = call_node.block
|
|
92
|
+
return false if block.nil?
|
|
93
|
+
return true unless block.is_a?(Prism::BlockNode)
|
|
94
|
+
# A parameter default (`|v = e.next|`) is evaluated on every iteration, since `loop` yields no
|
|
95
|
+
# arguments. Rather than walk the parameter list, any declared parameter widens.
|
|
96
|
+
return true if block.parameters
|
|
97
|
+
return false if block.body.nil?
|
|
98
|
+
|
|
99
|
+
may_raise_stop_iteration?(block.body)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def may_raise_stop_iteration?(node)
|
|
103
|
+
return true unless STOP_ITERATION_FREE_NODES.any? { |klass| node.is_a?(klass) }
|
|
104
|
+
|
|
105
|
+
node.compact_child_nodes.any? { |child| may_raise_stop_iteration?(child) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Cheap name-only pre-gate, so a call that cannot be catalogued pays nothing further.
|
|
109
|
+
def candidate_name?(method_name)
|
|
110
|
+
CANDIDATE_NAMES.include?(method_name)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def exactly_once_owner?(owner_name, method_name)
|
|
114
|
+
methods = EXACTLY_ONCE_IMMEDIATE[owner_name.to_s.delete_prefix("::")]
|
|
115
|
+
methods ? methods.include?(method_name) : false
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Whether a call of `method_name` on `receiver_type` reaches a catalogued exactly-once declaration.
|
|
119
|
+
#
|
|
120
|
+
# Gated on the RESOLVED owner, not the name: a class that defines its own `tap` (in source, in a
|
|
121
|
+
# project `sig/`, or through a project ancestor), a top-level `def tap` (a private `Object` method,
|
|
122
|
+
# ahead of `Kernel` in every MRO) and an `Object` / `Kernel` monkey-patch all decline. A union receiver
|
|
123
|
+
# qualifies only when every member does; `Dynamic` and the other carriers that name no class decline.
|
|
124
|
+
def exactly_once_call?(receiver_type:, method_name:, scope:)
|
|
125
|
+
return false unless candidate_name?(method_name)
|
|
126
|
+
return false if scope.nil? || project_redefines_root?(method_name, scope)
|
|
127
|
+
|
|
128
|
+
targets = receiver_targets(receiver_type)
|
|
129
|
+
return false if targets.nil? || targets.empty?
|
|
130
|
+
|
|
131
|
+
targets.all? { |class_name, kind| resolves_to_catalogue?(class_name, kind, method_name, scope) }
|
|
132
|
+
rescue StandardError
|
|
133
|
+
false
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Syntactic proof that a block body cannot complete normally: every path through it ends in a
|
|
137
|
+
# block-level `break` (which targets the yielding call), `return`, `redo`, or a non-returning Kernel call
|
|
138
|
+
# ({NON_RETURNING_CALLS}) — or in an expression that must evaluate one of those first. This is ANDed
|
|
139
|
+
# with the block-return pass's `bot`, never used alone, because a `bot` can also flow out of a callee's
|
|
140
|
+
# declared return that is not a promise the call never completes (`Kernel#loop` is `-> bot`, yet a
|
|
141
|
+
# `StopIteration` from `e.next` ends it normally). A shape the walk does not recognise declines.
|
|
142
|
+
#
|
|
143
|
+
# Only unconditionally evaluated children are descended: a nested block, lambda, `def` or loop is never
|
|
144
|
+
# entered (its body may not run, and it retargets `break`), a call's block is not a child the call
|
|
145
|
+
# promises to run, and `&&` / `||` count only their left operand. A `begin` with `rescue` qualifies only
|
|
146
|
+
# when its body and every rescue clause must exit; an `ensure` that must exit qualifies on its own.
|
|
147
|
+
def never_completes_normally?(node, scope)
|
|
148
|
+
case node
|
|
149
|
+
when Prism::StatementsNode then node.body.any? { |statement| never_completes_normally?(statement, scope) }
|
|
150
|
+
when Prism::BreakNode, Prism::ReturnNode, Prism::RedoNode, Prism::RetryNode then true
|
|
151
|
+
when Prism::ParenthesesNode then never_completes_normally?(node.body, scope)
|
|
152
|
+
when Prism::CallNode then call_never_returns?(node, scope)
|
|
153
|
+
when Prism::IfNode, Prism::UnlessNode then conditional_never_completes?(node, scope)
|
|
154
|
+
when Prism::AndNode, Prism::OrNode then never_completes_normally?(node.left, scope)
|
|
155
|
+
when Prism::BeginNode then begin_never_completes?(node, scope)
|
|
156
|
+
when Prism::ArrayNode then node.elements.any? { |element| never_completes_normally?(element, scope) }
|
|
157
|
+
when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode
|
|
158
|
+
never_completes_normally?(node.value, scope)
|
|
159
|
+
else false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Deliberately coarse: ANY project definition of the name — top-level, on any class or module, either
|
|
164
|
+
# side, or a `pre_eval:` patch — declines. Resolving the call-site `self`'s ancestry precisely buys
|
|
165
|
+
# nothing for names this rare, and the block-return pass is no independent check here: it types a
|
|
166
|
+
# self-call to an overridden `raise` as Kernel's `bot` too, so a missed override is a wrong `bot`.
|
|
167
|
+
# {LastLine.reads_line?} declines on it the same way for `gets` and `readline`.
|
|
168
|
+
def project_defines_anywhere?(method_name, scope)
|
|
169
|
+
return true if scope.top_level_def_for(method_name)
|
|
170
|
+
# `discovered_methods` withholds a plain cross-file `def` (the ADR-17 monkey-patch contract); the
|
|
171
|
+
# def-node tables still carry it, on both sides.
|
|
172
|
+
return true if [scope.discovered_methods, scope.discovered_def_nodes, scope.discovered_singleton_def_nodes]
|
|
173
|
+
.any? { |tables| tables.any? { |_class_name, table| table.key?(method_name) } }
|
|
174
|
+
|
|
175
|
+
patched = scope.environment&.project_patched_methods
|
|
176
|
+
!patched.nil? && patched.by_key.any? { |(_class_name, name, _kind), _entry| name == method_name }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
class << self
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def call_never_returns?(node, scope)
|
|
183
|
+
return true if never_completes_normally?(node.receiver, scope)
|
|
184
|
+
return true if node.arguments&.arguments&.any? { |argument| never_completes_normally?(argument, scope) }
|
|
185
|
+
return false unless NON_RETURNING_CALLS.include?(node.name)
|
|
186
|
+
|
|
187
|
+
kernel_spelled_receiver?(node.receiver) && !project_defines_anywhere?(node.name, scope)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Implicit self, `self.`, or the `Kernel` module itself (`Kernel.` or the root-anchored `::Kernel.`) —
|
|
191
|
+
# the spellings that reach Kernel's function.
|
|
192
|
+
def kernel_spelled_receiver?(receiver)
|
|
193
|
+
case receiver
|
|
194
|
+
when nil, Prism::SelfNode then true
|
|
195
|
+
when Prism::ConstantReadNode then receiver.name == :Kernel
|
|
196
|
+
when Prism::ConstantPathNode then receiver.parent.nil? && receiver.name == :Kernel
|
|
197
|
+
else false
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def conditional_never_completes?(node, scope)
|
|
202
|
+
return true if never_completes_normally?(node.predicate, scope)
|
|
203
|
+
|
|
204
|
+
alternative = node.is_a?(Prism::IfNode) ? node.subsequent : node.else_clause
|
|
205
|
+
return false if node.statements.nil? || alternative.nil?
|
|
206
|
+
|
|
207
|
+
never_completes_normally?(node.statements, scope) && branch_never_completes?(alternative, scope)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def branch_never_completes?(branch, scope)
|
|
211
|
+
case branch
|
|
212
|
+
when Prism::ElseNode then never_completes_normally?(branch.statements, scope)
|
|
213
|
+
else never_completes_normally?(branch, scope)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def begin_never_completes?(node, scope)
|
|
218
|
+
return true if node.ensure_clause && never_completes_normally?(node.ensure_clause.statements, scope)
|
|
219
|
+
return false unless never_completes_normally?(node.statements, scope)
|
|
220
|
+
|
|
221
|
+
rescue_clause = node.rescue_clause
|
|
222
|
+
while rescue_clause
|
|
223
|
+
return false unless never_completes_normally?(rescue_clause.statements, scope)
|
|
224
|
+
|
|
225
|
+
rescue_clause = rescue_clause.subsequent
|
|
226
|
+
end
|
|
227
|
+
true
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def project_redefines_root?(method_name, scope)
|
|
231
|
+
return true if scope.top_level_def_for(method_name)
|
|
232
|
+
|
|
233
|
+
patched = scope.environment&.project_patched_methods
|
|
234
|
+
PATCHABLE_ROOTS.any? do |root|
|
|
235
|
+
scope.discovered_method?(root, method_name, :instance) ||
|
|
236
|
+
patched&.lookup(class_name: root, method_name: method_name, kind: :instance)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# `[class_name, :instance | :singleton]` pairs the receiver can dispatch through, or nil when some
|
|
241
|
+
# part of it names no class. `Tuple` / `HashShape` project to `Array` / `Hash`, `Constant[v]` through
|
|
242
|
+
# the value's class, the same projection {ClosureEscapeAnalyzer} uses.
|
|
243
|
+
def receiver_targets(receiver_type)
|
|
244
|
+
case receiver_type
|
|
245
|
+
when Type::Union
|
|
246
|
+
members = receiver_type.members.map { |member| receiver_targets(member) }
|
|
247
|
+
members.any?(&:nil?) ? nil : members.flatten(1)
|
|
248
|
+
when Type::Nominal then [[receiver_type.class_name, :instance]]
|
|
249
|
+
when Type::Singleton then [[receiver_type.class_name, :singleton]]
|
|
250
|
+
when Type::Tuple then [["Array", :instance]]
|
|
251
|
+
when Type::HashShape then [["Hash", :instance]]
|
|
252
|
+
when Type::Constant then [[receiver_type.value.class.name, :instance]]
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def resolves_to_catalogue?(class_name, kind, method_name, scope)
|
|
257
|
+
return false if class_name.nil?
|
|
258
|
+
return false if scope.discovered_method_through_ancestors?(class_name, method_name, kind)
|
|
259
|
+
|
|
260
|
+
if kind == :singleton
|
|
261
|
+
definition = Rigor::Reflection.singleton_method_definition(class_name, method_name, scope: scope)
|
|
262
|
+
return false if singleton_ancestor_patched?(class_name, method_name, scope)
|
|
263
|
+
|
|
264
|
+
return declared_on_catalogue?(definition, method_name)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
definition = Rigor::Reflection.instance_method_definition(class_name, method_name, scope: scope)
|
|
268
|
+
if definition
|
|
269
|
+
return false if rbs_ancestor_patched?(class_name, method_name, scope)
|
|
270
|
+
|
|
271
|
+
return declared_on_catalogue?(definition, method_name)
|
|
272
|
+
end
|
|
273
|
+
return false if Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
274
|
+
|
|
275
|
+
project_class_resolves_to_catalogue?(class_name, method_name, scope)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def declared_on_catalogue?(definition, method_name)
|
|
279
|
+
return false if definition.nil? || !definition.respond_to?(:defined_in)
|
|
280
|
+
|
|
281
|
+
exactly_once_owner?(definition.defined_in, method_name)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# A project class the RBS environment does not know: its project ancestry was already cleared by the
|
|
285
|
+
# discovery walk, so the answer rests on the ancestors it reaches OUTSIDE the project. Each is asked
|
|
286
|
+
# on its own terms; an ancestor nobody can resolve is uncertainty and declines. No external ancestor
|
|
287
|
+
# at all is the implicit `< Object`, which reaches `Kernel`.
|
|
288
|
+
def project_class_resolves_to_catalogue?(class_name, method_name, scope)
|
|
289
|
+
return false unless scope.known_user_class?(class_name)
|
|
290
|
+
|
|
291
|
+
scope.external_ancestor_name_candidates(class_name).all? do |candidates|
|
|
292
|
+
external_ancestor_resolves_to_catalogue?(candidates, method_name, scope)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def external_ancestor_resolves_to_catalogue?(candidates, method_name, scope)
|
|
297
|
+
known = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: scope) }
|
|
298
|
+
return false if known.nil?
|
|
299
|
+
|
|
300
|
+
definition = Rigor::Reflection.instance_method_definition(known, method_name, scope: scope)
|
|
301
|
+
# A mixin whose RBS does not mention the method contributes nothing; a CLASS that lacks it is a
|
|
302
|
+
# `BasicObject` lineage, where the call does not reach `Kernel` at all.
|
|
303
|
+
return false if rbs_ancestor_patched?(known, method_name, scope)
|
|
304
|
+
return scope.environment.rbs_module?(known) if definition.nil?
|
|
305
|
+
|
|
306
|
+
declared_on_catalogue?(definition, method_name)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# The RBS declaration says where the method was DECLARED; a project reopening of a core ancestor
|
|
310
|
+
# (`module Enumerable; def tap = :x; end`) redefines it without touching RBS, and discovery records
|
|
311
|
+
# it under the ancestor's own name. So every RBS ancestor — mixins included — is asked.
|
|
312
|
+
def rbs_ancestor_patched?(class_name, method_name, scope)
|
|
313
|
+
names_patched?(rbs_ancestor_names(class_name, scope), method_name, :instance, scope)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# A class object dispatches through its own and its superclasses' singleton methods, then through
|
|
317
|
+
# `Class`, `Module`, `Object`, `Kernel` as instance methods.
|
|
318
|
+
def singleton_ancestor_patched?(class_name, method_name, scope)
|
|
319
|
+
names_patched?(rbs_ancestor_names(class_name, scope), method_name, :singleton, scope) ||
|
|
320
|
+
names_patched?(SINGLETON_ROOTS, method_name, :instance, scope)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def names_patched?(names, method_name, kind, scope)
|
|
324
|
+
patched = scope.environment&.project_patched_methods
|
|
325
|
+
names.any? do |name|
|
|
326
|
+
scope.discovered_method?(name, method_name, kind) ||
|
|
327
|
+
patched&.lookup(class_name: name, method_name: method_name, kind: kind)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def rbs_ancestor_names(class_name, scope)
|
|
332
|
+
loader = scope.environment&.rbs_loader
|
|
333
|
+
loader ? loader.ancestor_names_for(class_name.to_s) : []
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
5
|
require_relative "../type"
|
|
6
|
+
require_relative "block_auto_splat"
|
|
6
7
|
require_relative "multi_target_binder"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
@@ -22,31 +23,56 @@ module Rigor
|
|
|
22
23
|
# MultiTargetNode parameters (`|(a, b), c|`) are bound by delegating each destructuring slot to
|
|
23
24
|
# {Rigor::Inference::MultiTargetBinder}, so a Tuple-shaped expected element type projects element-wise
|
|
24
25
|
# into the inner locals (Slice 6 phase C sub-phase 2). Numbered parameters (`_1`, `_2`, ...) are bound
|
|
25
|
-
# from `Prism::NumberedParametersNode`
|
|
26
|
-
# `
|
|
26
|
+
# from `Prism::NumberedParametersNode` as the explicit list of that many required positionals, the
|
|
27
|
+
# highest `_N` the body references being the arity (CRuby's own reading: `proc { _2 }.parameters` is
|
|
28
|
+
# `[[:opt, :_1], [:opt, :_2]]`, and `lambda { _2 }.arity` is 2). So `h.each { _1; _2 }` auto-splats like
|
|
29
|
+
# `|k, v|`, while a body that uses only `_1` reads as `|a|` and does not (issue #1108).
|
|
27
30
|
#
|
|
28
31
|
# The `it` implicit parameter (Ruby 3.4+) is bound from `Prism::ItParametersNode`. It is the
|
|
29
32
|
# single-argument cousin of `_1`: the binder produces `{ it: expected_param_types[0] }` so the body's
|
|
30
|
-
# `Prism::ItLocalVariableReadNode` lookup sees the same type as the explicit `|x|` form would
|
|
33
|
+
# `Prism::ItLocalVariableReadNode` lookup sees the same type as the explicit `|x|` form would, and it
|
|
34
|
+
# never auto-splats.
|
|
35
|
+
#
|
|
36
|
+
# A block whose parameter list CRuby splats spreads the one yielded value across its positionals;
|
|
37
|
+
# {BlockAutoSplat} owns that rule and its carrier arms, and this binder only applies the table it
|
|
38
|
+
# answers. Every name the spread binds from an `Array[T]` slot — and every name a nested `|(g, h)|`
|
|
39
|
+
# destructure binds from one — is reported as optimistic, and {#bind_onto} records those through
|
|
40
|
+
# `Scope#with_optimistic_local` exactly as the statement-level `a, b = ints` does (issue #1093; the
|
|
41
|
+
# shared rule lives in {MultiTargetBinder}). A nested `|(g, h)|` destructures the one value the
|
|
42
|
+
# signature already placed, so it wraps like the statement form.
|
|
31
43
|
#
|
|
32
44
|
# Block-local declarations after `;` (e.g., `|x; y, z|`) are still skipped — they are explicitly
|
|
33
45
|
# block-local, so the outer scope MUST NOT observe them and the binder leaves them unbound.
|
|
34
46
|
#
|
|
35
47
|
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
36
48
|
class BlockParameterBinder
|
|
37
|
-
# @param expected_param_types
|
|
49
|
+
# @param expected_param_types — positional block parameter types in order. Indices
|
|
38
50
|
# the binder cannot fill from this array (because the array is shorter than the parameter list, or
|
|
39
51
|
# because the slot is a kind we do not pull from the array) default to `Dynamic[Top]`.
|
|
40
52
|
def initialize(expected_param_types: [])
|
|
41
|
-
@
|
|
53
|
+
@declared_param_types = expected_param_types
|
|
54
|
+
reset_per_bind_state
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The names the last {#bind} bound optimistically nil-free (see the class comment).
|
|
58
|
+
attr_reader :optimistic
|
|
59
|
+
|
|
60
|
+
# Binds the block's parameters into `scope`: {#bind}'s types through `Scope#with_local`, then the
|
|
61
|
+
# optimistic mark for every name in {#optimistic}.
|
|
62
|
+
def bind_onto(block_node, scope)
|
|
63
|
+
types = bind(block_node, scope: scope)
|
|
64
|
+
MultiTargetBinder::Result.new(types: types, optimistic: @optimistic.dup.freeze).apply_to(scope)
|
|
42
65
|
end
|
|
43
66
|
|
|
44
|
-
# @
|
|
45
|
-
# @return [Hash{Symbol => Rigor::Type}] ordered map from parameter name to bound type. Anonymous
|
|
67
|
+
# @return ordered map from parameter name to bound type. Anonymous
|
|
46
68
|
# parameters are skipped; MultiTargetNode destructuring slots delegate to {MultiTargetBinder} and
|
|
47
69
|
# contribute every named local in declaration order. Numbered-parameter forms (`_1`, `_2`, ...) bind
|
|
48
70
|
# `:_1`, `:_2`, ... up to the maximum the block body refers to.
|
|
49
|
-
|
|
71
|
+
# @param scope — the scope the block is entered from, which answers a nested destructure's `to_ary`
|
|
72
|
+
# question ({MultiTargetBinder.bind_marked}).
|
|
73
|
+
def bind(block_node, scope: nil)
|
|
74
|
+
reset_per_bind_state
|
|
75
|
+
@scope = scope
|
|
50
76
|
params_root = block_node.parameters
|
|
51
77
|
return {} if params_root.nil?
|
|
52
78
|
|
|
@@ -64,14 +90,29 @@ module Rigor
|
|
|
64
90
|
|
|
65
91
|
private
|
|
66
92
|
|
|
93
|
+
# {#apply_auto_splat} rewrites the positional table for the block it is binding, so every {#bind} starts
|
|
94
|
+
# from the declared types again; a binder reused across blocks must not see the previous block's splat.
|
|
95
|
+
def reset_per_bind_state
|
|
96
|
+
@expected_param_types = @declared_param_types
|
|
97
|
+
@splat_rest_type = nil
|
|
98
|
+
@optimistic_positions = []
|
|
99
|
+
@optimistic = []
|
|
100
|
+
@scope = nil
|
|
101
|
+
end
|
|
102
|
+
|
|
67
103
|
# `|_1, _2|` numbered-parameter form. Prism exposes the implicit count through
|
|
68
104
|
# `NumberedParametersNode#maximum` (the highest `_N` referenced in the body); we materialise bindings
|
|
69
|
-
# for `:_1` through `:_maximum
|
|
70
|
-
#
|
|
105
|
+
# for `:_1` through `:_maximum`, auto-splatting exactly as the explicit list of that many required
|
|
106
|
+
# positionals would, so the body's `LocalVariableReadNode` lookups see the same types and marks.
|
|
71
107
|
def bind_numbered_parameters(numbered_node)
|
|
108
|
+
arity = numbered_node.maximum
|
|
109
|
+
apply_auto_splat(BlockAutoSplat::ParameterShape.of_arity(arity))
|
|
110
|
+
|
|
72
111
|
bindings = {}
|
|
73
|
-
|
|
74
|
-
|
|
112
|
+
arity.times do |i|
|
|
113
|
+
name = :"_#{i + 1}"
|
|
114
|
+
bindings[name] = positional_type_at(i)
|
|
115
|
+
@optimistic << name if @optimistic_positions.include?(i)
|
|
75
116
|
end
|
|
76
117
|
bindings
|
|
77
118
|
end
|
|
@@ -86,7 +127,7 @@ module Rigor
|
|
|
86
127
|
params_node = params_root.parameters
|
|
87
128
|
return {} if params_node.nil?
|
|
88
129
|
|
|
89
|
-
apply_auto_splat(params_node)
|
|
130
|
+
apply_auto_splat(BlockAutoSplat::ParameterShape.of(params_node))
|
|
90
131
|
|
|
91
132
|
bindings = {}
|
|
92
133
|
bind_positionals(params_node, bindings, 0)
|
|
@@ -97,8 +138,8 @@ module Rigor
|
|
|
97
138
|
bindings
|
|
98
139
|
end
|
|
99
140
|
|
|
100
|
-
# Ruby blocks (NOT lambdas) auto-splat a single yielded
|
|
101
|
-
#
|
|
141
|
+
# Ruby blocks (NOT lambdas) auto-splat a single yielded array-ish value when the parameter list is
|
|
142
|
+
# one CRuby splats:
|
|
102
143
|
#
|
|
103
144
|
# { a: 1 }.each { |k, v| ... }
|
|
104
145
|
#
|
|
@@ -107,21 +148,21 @@ module Rigor
|
|
|
107
148
|
# the binder would assign `k = Tuple[K, V]` and `v = Dynamic[Top]`, and any call on
|
|
108
149
|
# `k.<method-not-on-Tuple>` would false-fire.
|
|
109
150
|
#
|
|
110
|
-
# The rule fires only when
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
def apply_auto_splat(
|
|
151
|
+
# The rule fires only when the receiver yields exactly one value (`expected_param_types.size == 1`)
|
|
152
|
+
# and {BlockAutoSplat} answers a table for that value and `shape`. A multi-arg yield (e.g.
|
|
153
|
+
# `each_with_index`'s `(element, index)` pair) is NOT auto-splatted — matching Ruby semantics where a
|
|
154
|
+
# multi-arg yield to a `|a, b, c|` block fills the extra slot with nil rather than splatting any
|
|
155
|
+
# element.
|
|
156
|
+
def apply_auto_splat(shape)
|
|
116
157
|
return unless @expected_param_types.size == 1
|
|
158
|
+
return unless shape.splats?
|
|
117
159
|
|
|
118
|
-
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
first = @expected_param_types[0]
|
|
122
|
-
return unless first.is_a?(Type::Tuple)
|
|
160
|
+
table = BlockAutoSplat.for(shape, @expected_param_types[0])
|
|
161
|
+
return if table.nil?
|
|
123
162
|
|
|
124
|
-
@expected_param_types =
|
|
163
|
+
@expected_param_types = table.types
|
|
164
|
+
@optimistic_positions = table.optimistic_positions
|
|
165
|
+
@splat_rest_type = table.rest_type
|
|
125
166
|
end
|
|
126
167
|
|
|
127
168
|
def bind_positionals(params_node, bindings, cursor)
|
|
@@ -161,7 +202,8 @@ module Rigor
|
|
|
161
202
|
rest = params_node.rest
|
|
162
203
|
return unless rest.respond_to?(:name) && rest&.name
|
|
163
204
|
|
|
164
|
-
bindings[rest.name] =
|
|
205
|
+
bindings[rest.name] =
|
|
206
|
+
@splat_rest_type || Type::Combinator.nominal_of("Array", type_args: [Type::Combinator.untyped])
|
|
165
207
|
end
|
|
166
208
|
|
|
167
209
|
def bind_keywords(params_node, bindings)
|
|
@@ -200,9 +242,12 @@ module Rigor
|
|
|
200
242
|
case param
|
|
201
243
|
when Prism::RequiredParameterNode
|
|
202
244
|
bindings[param.name] = positional_type_at(cursor)
|
|
245
|
+
@optimistic << param.name if @optimistic_positions.include?(cursor)
|
|
203
246
|
when Prism::MultiTargetNode
|
|
204
|
-
nested = MultiTargetBinder.
|
|
205
|
-
|
|
247
|
+
nested = MultiTargetBinder.bind_marked(param, positional_type_at(cursor),
|
|
248
|
+
optimistic: @optimistic_positions.include?(cursor), scope: @scope)
|
|
249
|
+
bindings.merge!(nested.types)
|
|
250
|
+
@optimistic.concat(nested.optimistic)
|
|
206
251
|
end
|
|
207
252
|
end
|
|
208
253
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
require_relative "block_call_timing"
|
|
5
|
+
require_relative "closure_escape_analyzer"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
# Whether a block-accepting call may run its block more than once, so a later run reads a captured binding an
|
|
10
|
+
# earlier run moved. Two passes ask it and share this one rule: the block-return pass
|
|
11
|
+
# (`ExpressionTyper#block_may_repeat?`), which lays the #587 (b) captured binding under such a block, and the
|
|
12
|
+
# statement pass (`StatementEvaluator#repeating_block_entry`, issue #1412), which lays the ADR-56 WD2.13
|
|
13
|
+
# content-mutation widening on its entry.
|
|
14
|
+
#
|
|
15
|
+
# The core iteration methods {ClosureEscapeAnalyzer} catalogues as non-escaping prove it — for a project class
|
|
16
|
+
# too, through its ancestry (`include Enumerable`); `tap` / `then` / `yield_self` are catalogued there too but
|
|
17
|
+
# run their block exactly once ({BlockCallTiming}).
|
|
18
|
+
#
|
|
19
|
+
# Issue #1234 — a receiver the analyzer cannot classify at all (`Dynamic`, a union, a project class whose
|
|
20
|
+
# ancestry it cannot follow) repeats when the method NAME is a catalogued iterator the project does not define
|
|
21
|
+
# on it ({ClosureEscapeAnalyzer.repeats_by_name?}): `items.all? { seen += 1; seen == 1 }` on an untyped `items`
|
|
22
|
+
# kept the first run's `seen == 1`, the predicate folded to `true`, and the condition on the result was
|
|
23
|
+
# reported as constant. This is a reading of `:unknown` for these two passes alone; escape analysis still
|
|
24
|
+
# treats it as unproven. Any other name, and a receiver classified `:escaping`, answers false, which keeps the
|
|
25
|
+
# entry scope: it is exact for a block run once (`m.synchronize { out = buf; buf = nil; out }` is `buf`'s value,
|
|
26
|
+
# and a cross-iteration binding would add the `nil` a second run never reads).
|
|
27
|
+
#
|
|
28
|
+
# A receiver that provably holds at most one element runs the block at most once however it iterates, so it
|
|
29
|
+
# answers false too: `done = false; [:only].each { break if done; done = true }` is `[:only]`, and the
|
|
30
|
+
# cross-iteration `done` would have typed it `Array[Symbol]?`.
|
|
31
|
+
module BlockRepetition
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# `classification` is the receiver's {ClosureEscapeAnalyzer.classify} answer when the caller already holds it.
|
|
35
|
+
def may_repeat?(method_name:, receiver_type:, scope:, classification: nil)
|
|
36
|
+
return false if BlockCallTiming.candidate_name?(method_name)
|
|
37
|
+
return false if at_most_one_run?(method_name, receiver_type)
|
|
38
|
+
|
|
39
|
+
classification ||= ClosureEscapeAnalyzer.classify(
|
|
40
|
+
receiver_type: receiver_type, method_name: method_name, scope: scope
|
|
41
|
+
)
|
|
42
|
+
case classification
|
|
43
|
+
when :non_escaping then true
|
|
44
|
+
when :unknown
|
|
45
|
+
ClosureEscapeAnalyzer.repeats_by_name?(receiver_type: receiver_type, method_name: method_name, scope: scope)
|
|
46
|
+
else false
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def at_most_one_run?(method_name, receiver_type)
|
|
51
|
+
case receiver_type
|
|
52
|
+
when Type::Tuple then receiver_type.elements.size <= 1
|
|
53
|
+
when Type::HashShape then receiver_type.closed? && receiver_type.pairs.size <= 1
|
|
54
|
+
when Type::Constant then constant_at_most_one_run?(method_name, receiver_type.value)
|
|
55
|
+
else false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# `1.times` and a one-element integer range iterate once.
|
|
60
|
+
def constant_at_most_one_run?(method_name, value)
|
|
61
|
+
case value
|
|
62
|
+
when Integer then method_name == :times && value <= 1
|
|
63
|
+
when Range
|
|
64
|
+
value.begin.is_a?(Integer) && value.end.is_a?(Integer) &&
|
|
65
|
+
value.end - value.begin + (value.exclude_end? ? 0 : 1) <= 1
|
|
66
|
+
else false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -19,19 +19,20 @@ module Rigor
|
|
|
19
19
|
# The mechanism is parameterized over an `evaluate_body` callable so slice B can reuse it: given the
|
|
20
20
|
# current per-name bindings it returns the per-name exit bindings produced by one body evaluation from
|
|
21
21
|
# those bindings (names the body leaves unwritten in a given pass simply do not appear in the returned
|
|
22
|
-
# hash).
|
|
22
|
+
# hash). Slice C's content join reuses it too, with each "name" one evidence slot of a collection
|
|
23
|
+
# (`StatementEvaluator#join_content_to_fixpoint`) and a `bot` seed standing for "nothing stored yet".
|
|
23
24
|
module BodyFixpoint
|
|
24
25
|
# One body evaluation per iteration; ADR-55's shape (cap 3).
|
|
25
26
|
CAP = 3
|
|
26
27
|
|
|
27
28
|
module_function
|
|
28
29
|
|
|
29
|
-
# @param names
|
|
30
|
-
# @param seed_bindings
|
|
31
|
-
# @param widen
|
|
32
|
-
# @param evaluate_body
|
|
30
|
+
# @param names — the outer locals the body can rebind.
|
|
31
|
+
# @param seed_bindings — pre-state binding per name.
|
|
32
|
+
# @param widen — value-pinned widener (Constant -> Nominal).
|
|
33
|
+
# @param evaluate_body — `bindings -> exit_bindings` — evaluates the body once from `bindings`
|
|
33
34
|
# (the per-name current assumption) and returns the per-name exit binding it produced.
|
|
34
|
-
# @return
|
|
35
|
+
# @return the continuation binding per name.
|
|
35
36
|
def converge(names:, seed_bindings:, widen:, evaluate_body:)
|
|
36
37
|
return {} if names.empty?
|
|
37
38
|
|
|
@@ -49,7 +49,8 @@ module Rigor
|
|
|
49
49
|
RECURSION_FIXPOINT_CAP = :recursion_fixpoint_cap
|
|
50
50
|
# `BodyFixpoint#converge` (ADR-56 slice A — non-escaping block captured-local write-back) ran its
|
|
51
51
|
# 3-evaluation cap without the written local's join converging and collapsed that local to
|
|
52
|
-
# `Dynamic[top]` (the escaping-block floor). Shared by slice B's loop-body fixpoint
|
|
52
|
+
# `Dynamic[top]` (the escaping-block floor). Shared by slice B's loop-body fixpoint and by slice C's
|
|
53
|
+
# content-evidence fixpoint, where the collapsed name is one element / key / value slot of a collection join.
|
|
53
54
|
BLOCK_WRITEBACK_CAP = :block_writeback_cap
|
|
54
55
|
|
|
55
56
|
# ADR-57 return-memo profile counters (not cutoffs — see the module doc). All bumped by
|
|
@@ -65,8 +66,6 @@ module Rigor
|
|
|
65
66
|
# plain `(receiver, method)` signature was already on the recursion guard stack.
|
|
66
67
|
# - {MEMO_REFUSE_UNROLL} — pre-ADR-84 blanket refusal (constant-arg unroll in flight). Kept in the table
|
|
67
68
|
# as the ADR-84 WD3 gate pin: structurally unreachable, must read ~0.
|
|
68
|
-
# - {MEMO_REFUSE_CONSULT_TAINTED} — a candidate frame computed a result but an ADR-55 fixpoint summary
|
|
69
|
-
# was *consulted* during the compute, so the result is a transient Kleene iterate and was NOT stored.
|
|
70
69
|
# - {MEMO_REFUSE_TRANSIENT} — ADR-84 WD3: a candidate frame's compute bracket saw a
|
|
71
70
|
# transient-machinery event (recursion-guard hit / unroll-fuel exhaustion / ADR-55 WD1 clamp /
|
|
72
71
|
# fixpoint-cap collapse — `ExpressionTyper#note_transient_fallback`) that referenced a stack frame
|
|
@@ -79,15 +78,13 @@ module Rigor
|
|
|
79
78
|
MEMO_BODY_EVALS = :memo_body_evals
|
|
80
79
|
MEMO_REFUSE_ON_STACK = :memo_refuse_on_stack
|
|
81
80
|
MEMO_REFUSE_UNROLL = :memo_refuse_unroll
|
|
82
|
-
MEMO_REFUSE_CONSULT_TAINTED = :memo_refuse_consult_tainted
|
|
83
81
|
MEMO_REFUSE_TRANSIENT = :memo_refuse_transient
|
|
84
82
|
|
|
85
83
|
CATEGORIES = [
|
|
86
84
|
RECURSION_GUARD, ANCESTOR_WALK_LIMIT, HKT_FUEL_EXHAUSTED, RECURSION_UNROLL_FUEL,
|
|
87
85
|
RECURSION_FIXPOINT_CAP, BLOCK_WRITEBACK_CAP,
|
|
88
86
|
MEMO_ENTRIES, MEMO_HITS, MEMO_MISSES, MEMO_BODY_EVALS,
|
|
89
|
-
MEMO_REFUSE_ON_STACK, MEMO_REFUSE_UNROLL,
|
|
90
|
-
MEMO_REFUSE_TRANSIENT
|
|
87
|
+
MEMO_REFUSE_ON_STACK, MEMO_REFUSE_UNROLL, MEMO_REFUSE_TRANSIENT
|
|
91
88
|
].freeze
|
|
92
89
|
|
|
93
90
|
# Distribution (histogram) categories — read-only observations of a value's size at a site, used to
|