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
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../type"
|
|
4
|
+
require_relative "../reflection"
|
|
5
|
+
require_relative "external_ancestor_resolution"
|
|
6
|
+
require_relative "project_method_ownership"
|
|
4
7
|
|
|
5
8
|
module Rigor
|
|
6
9
|
module Inference
|
|
@@ -30,14 +33,19 @@ module Rigor
|
|
|
30
33
|
# therefore covers ONLY the core-and-stdlib surface where immediate invocation is part of the documented
|
|
31
34
|
# contract:
|
|
32
35
|
#
|
|
33
|
-
# - `Array`, `Hash`, `Range`, `
|
|
34
|
-
# `reject`, `flat_map`, `find`/`detect`, `any?`, `all?`, `none?`, `one?`, `count`,
|
|
35
|
-
# `each_with_index`, `each_with_object`, `min_by`, `max_by`, `sort_by`, `partition`,
|
|
36
|
-
# `tally`, `sum`, `take_while`, `drop_while`, `chunk_while`, `slice_when`, `zip`, `collect`,
|
|
36
|
+
# - `Array`, `Hash`, `Range`, `Set`, `Enumerator`, `Enumerator::Lazy` iteration methods (`each`, `map`,
|
|
37
|
+
# `select`, `reject`, `flat_map`, `find`/`detect`, `any?`, `all?`, `none?`, `one?`, `count`,
|
|
38
|
+
# `inject`/`reduce`, `each_with_index`, `each_with_object`, `min_by`, `max_by`, `sort_by`, `partition`,
|
|
39
|
+
# `group_by`, `tally`, `sum`, `take_while`, `drop_while`, `chunk_while`, `slice_when`, `zip`, `collect`,
|
|
37
40
|
# `collect_concat`, `filter`, `filter_map`).
|
|
38
41
|
# - `Hash`-only iteration: `each_pair`, `each_key`, `each_value`, `transform_keys`, `transform_values`.
|
|
39
42
|
# - `Integer#times`, `Integer#upto`, `Integer#downto`, `Range#each`, `Range#step`.
|
|
40
|
-
# - `
|
|
43
|
+
# - `IO` / `File` / `StringIO` line iteration (`each`, `each_line`, `each_byte`, `each_char`,
|
|
44
|
+
# `each_codepoint`, the singleton `foreach`) and the same Enumerable methods minus
|
|
45
|
+
# {DEFERRED_ENUMERATOR_METHODS}.
|
|
46
|
+
# - `Object#tap`, `Object#then`, `Object#yield_self`. The stronger "yields exactly once, before returning"
|
|
47
|
+
# fact for these three lives in {BlockCallTiming}; that table is expected to move to the same
|
|
48
|
+
# `RBS::Extended` call-timing effect as this one.
|
|
41
49
|
# - Tuple/HashShape carriers map to Array/Hash for catalogue lookup so a literal `[1, 2, 3].each { ... }`
|
|
42
50
|
# is recognised.
|
|
43
51
|
#
|
|
@@ -46,32 +54,80 @@ module Rigor
|
|
|
46
54
|
# retained. False positives in this catalogue would silently weaken the soundness of fact retention in
|
|
47
55
|
# later sub-phases.
|
|
48
56
|
#
|
|
57
|
+
# Issue #1234 — a project class is outside the catalogue by name, yet `class Shelf; include Enumerable`
|
|
58
|
+
# answers `find` with `Enumerable#find` all the same. Given the `scope:` whose discovery tables know the
|
|
59
|
+
# project, a `Nominal` receiver of a project class classifies through its ancestry
|
|
60
|
+
# ({.ancestry_non_escaping?}): the method must be one the project does not define anywhere in that
|
|
61
|
+
# ancestry, and the first ancestor outside the project that declares it must be a catalogued class or
|
|
62
|
+
# `Enumerable` ({MIXIN_NON_ESCAPING}). An ancestor the RBS environment does not know could declare
|
|
63
|
+
# anything, so meeting one first declines.
|
|
64
|
+
#
|
|
49
65
|
# The analyzer is a pure query. It MUST NOT mutate the receiver type or scope, MUST NOT raise on
|
|
50
66
|
# unrecognised inputs, and MUST be deterministic for a given input.
|
|
51
67
|
module ClosureEscapeAnalyzer
|
|
52
68
|
module_function
|
|
53
69
|
|
|
54
|
-
# @param
|
|
55
|
-
# @param method_name [Symbol]
|
|
56
|
-
# @param environment [Rigor::Environment, nil] reserved for the future sub-phase that consults
|
|
70
|
+
# @param environment — reserved for the future sub-phase that consults
|
|
57
71
|
# `RBS::Extended` call-timing effects; sub-phase 3a ignores it.
|
|
58
|
-
# @
|
|
59
|
-
|
|
72
|
+
# @param scope — the project's discovery tables, for the ancestry step; without it a project class
|
|
73
|
+
# stays `:unknown`.
|
|
74
|
+
# @return one of `:non_escaping`, `:escaping`, `:unknown`.
|
|
75
|
+
def classify(receiver_type:, method_name:, environment: nil, scope: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
60
76
|
return :unknown if receiver_type.nil?
|
|
61
77
|
|
|
78
|
+
method_sym = method_name.to_sym
|
|
62
79
|
class_name = receiver_class_name(receiver_type)
|
|
63
|
-
|
|
80
|
+
if class_name
|
|
81
|
+
return :non_escaping if non_escaping?(class_name, method_sym)
|
|
82
|
+
return :escaping if escaping?(class_name, method_sym)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
instance_class = instance_carrier_class_name(receiver_type)
|
|
86
|
+
return :unknown if instance_class.nil?
|
|
87
|
+
|
|
88
|
+
ancestry_non_escaping?(instance_class, method_sym, scope) ? :non_escaping : :unknown
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Issue #1234 — whether some catalogue entry lists `method_name` as an iteration method: a name that runs
|
|
92
|
+
# its block once per element wherever the catalogue knows the receiver. `tap` / `then` / `yield_self` are
|
|
93
|
+
# left out, as they run the block exactly once. This is a fact about the NAME, for the one consumer that
|
|
94
|
+
# asks it of an `:unknown` receiver ({BlockRepetition.may_repeat?}); it proves nothing about escape.
|
|
95
|
+
def iterator_name?(method_name)
|
|
96
|
+
ITERATOR_NAMES.include?(method_name)
|
|
97
|
+
end
|
|
64
98
|
|
|
99
|
+
# Issue #1234 — whether the NAME of a catalogued iterator is the only thing Rigor knows about the method
|
|
100
|
+
# `receiver_type` answers `method_name` with, so a pass may read it as repetition
|
|
101
|
+
# ({BlockRepetition.may_repeat?}, for an `:unknown` receiver). It holds for a receiver Rigor cannot
|
|
102
|
+
# see at all (`Dynamic`, `Top`), and for a class it can see whose method the project does not define.
|
|
103
|
+
#
|
|
104
|
+
# A method the project defines under a catalogued name is the project's, not the iterator, so the name
|
|
105
|
+
# says nothing about how often it yields: `class Vault; def select(key) = yield(key.to_s); end` runs its
|
|
106
|
+
# block once. "Defines" is a `def`, `define_method` or `attr_*` anywhere in a project class's ancestry
|
|
107
|
+
# (instance side, or singleton side for a class-object receiver), or a signature whose declaring owner
|
|
108
|
+
# is a project class or module. A carrier this does not recognise, or one whose class it cannot name
|
|
109
|
+
# (an anonymous `Struct.new` value), answers false: when Rigor cannot tell whether the project owns the
|
|
110
|
+
# method, it does not assume repetition. {ProjectMethodOwnership.targets} carries the carrier audit.
|
|
111
|
+
def repeats_by_name?(receiver_type:, method_name:, scope:)
|
|
65
112
|
method_sym = method_name.to_sym
|
|
66
|
-
return
|
|
67
|
-
return :escaping if escaping?(class_name, method_sym)
|
|
113
|
+
return false unless ITERATOR_NAMES.include?(method_sym)
|
|
68
114
|
|
|
69
|
-
|
|
115
|
+
targets = ProjectMethodOwnership.targets(receiver_type)
|
|
116
|
+
return false if targets.nil?
|
|
117
|
+
|
|
118
|
+
targets.none? { |class_name, kind| ProjectMethodOwnership.defines?(class_name, method_sym, kind, scope) }
|
|
70
119
|
end
|
|
71
120
|
|
|
72
121
|
class << self
|
|
73
122
|
private
|
|
74
123
|
|
|
124
|
+
# The instance-side class a `Nominal` or an ADR-48 member carrier names, for the ancestry step.
|
|
125
|
+
def instance_carrier_class_name(receiver_type)
|
|
126
|
+
case receiver_type
|
|
127
|
+
when Type::Nominal, Type::StructInstance, Type::DataInstance then receiver_type.class_name&.to_s
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
75
131
|
# Resolve a single concrete class name for catalogue lookup. Returns `nil` when the receiver carrier
|
|
76
132
|
# does not name a single class (e.g. `Top`, `Dynamic[Top]`, `Union[...]`, `Bot`). `Tuple` projects to
|
|
77
133
|
# `Array`; `HashShape` to `Hash`; `Singleton[C]` to `C` (so `Integer.times` would resolve as a
|
|
@@ -117,6 +173,64 @@ module Rigor
|
|
|
117
173
|
methods = ESCAPING[class_name]
|
|
118
174
|
methods ? methods.include?(method_sym) : false
|
|
119
175
|
end
|
|
176
|
+
|
|
177
|
+
# Issue #1234 — a project class answers `method_sym` through the catalogued ancestor Ruby dispatches it
|
|
178
|
+
# to. The cheap gates come first: the name must be a catalogued iteration method, and the receiver a
|
|
179
|
+
# class the project declares. A definition anywhere in the project ancestry — the class's own `def
|
|
180
|
+
# find`, a project module's, a reopened `Enumerable`'s — is not the catalogued method and declines.
|
|
181
|
+
# A class the project's `sig/` declares asks its RBS definition where the method comes from; one
|
|
182
|
+
# without RBS walks the ancestors outside the project in method-resolution order.
|
|
183
|
+
def ancestry_non_escaping?(class_name, method_sym, scope)
|
|
184
|
+
return false if scope.nil? || !ITERATOR_NAMES.include?(method_sym)
|
|
185
|
+
return false unless scope.known_user_class?(class_name)
|
|
186
|
+
|
|
187
|
+
by_method = (ProjectMethodOwnership.memo(scope)[:ancestry][class_name] ||= {})
|
|
188
|
+
return by_method[method_sym] if by_method.key?(method_sym)
|
|
189
|
+
|
|
190
|
+
by_method[method_sym] = compute_ancestry_non_escaping?(class_name, method_sym, scope)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def compute_ancestry_non_escaping?(class_name, method_sym, scope)
|
|
194
|
+
return false if ProjectMethodOwnership.defines?(class_name, method_sym, :instance, scope)
|
|
195
|
+
|
|
196
|
+
if Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
197
|
+
return catalogued_declaration?(method_definition(class_name, method_sym, :instance, scope), method_sym)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
external_ancestry_non_escaping?(class_name, method_sym, scope)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# The RBS definition, or nil — a malformed signature is a gap, and
|
|
204
|
+
# {ExternalAncestorResolution.method_definition} is the one place that rescues it.
|
|
205
|
+
def method_definition(class_name, method_sym, kind, scope)
|
|
206
|
+
ExternalAncestorResolution.method_definition(class_name, method_sym, kind, scope: scope)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# The first external ancestor that declares the method decides. One the environment does not know may
|
|
210
|
+
# declare it, so it declines rather than being skipped; one that does not declare it is skipped.
|
|
211
|
+
def external_ancestry_non_escaping?(class_name, method_sym, scope)
|
|
212
|
+
scope.external_ancestor_name_candidates(class_name).each do |candidates|
|
|
213
|
+
known = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: scope) }
|
|
214
|
+
return false if known.nil?
|
|
215
|
+
return true if catalogued_owner?(known, method_sym)
|
|
216
|
+
|
|
217
|
+
definition = method_definition(known, method_sym, :instance, scope)
|
|
218
|
+
return catalogued_declaration?(definition, method_sym) if definition
|
|
219
|
+
end
|
|
220
|
+
false
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def catalogued_declaration?(definition, method_sym)
|
|
224
|
+
return false if definition.nil? || !definition.respond_to?(:defined_in)
|
|
225
|
+
|
|
226
|
+
owner = definition.defined_in
|
|
227
|
+
!owner.nil? && catalogued_owner?(owner.to_s.delete_prefix("::"), method_sym)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def catalogued_owner?(name, method_sym)
|
|
231
|
+
methods = NON_ESCAPING[name] || MIXIN_NON_ESCAPING[name]
|
|
232
|
+
methods ? methods.include?(method_sym) : false
|
|
233
|
+
end
|
|
120
234
|
end
|
|
121
235
|
|
|
122
236
|
OBJECT_NON_ESCAPING = %i[tap then yield_self].freeze
|
|
@@ -151,9 +265,24 @@ module Rigor
|
|
|
151
265
|
# `File.foreach(path) { case … when … then flag = true when … then return true if flag end }` classifies
|
|
152
266
|
# `:unknown` and misses the loop-body re-narrowing, so a local written in one `when` arm reads its
|
|
153
267
|
# pre-loop value in a sibling arm and a guarding condition folds to a spurious constant.
|
|
268
|
+
#
|
|
269
|
+
# The three are also `Enumerable[String]` over lines (rbs core declares it for `IO`;
|
|
270
|
+
# `data/core_overlay/string_io.rbs` for `StringIO`). An eager Enumerable method runs its block from inside
|
|
271
|
+
# the call, through `each`, so `io.each_with_index { … }` / `io.detect { … }` carry the same contract and
|
|
272
|
+
# take `ENUMERABLE_NON_ESCAPING` — minus {DEFERRED_ENUMERATOR_METHODS}, whose block outlives the call. On
|
|
273
|
+
# the `singleton(File)` / `singleton(IO)` side those names only ever meet `IO.select`, which takes no
|
|
274
|
+
# block and so never retains one. The key is the exact class name: a subclass (`Tempfile`, a user
|
|
275
|
+
# `StringIO` subclass) stays `:unknown`.
|
|
154
276
|
IO_ITERATION = %i[each_line each each_byte each_char each_codepoint].freeze
|
|
155
277
|
IO_SINGLETON_ITERATION = %i[foreach].freeze
|
|
156
278
|
|
|
279
|
+
# Enumerable methods that return an Enumerator holding the block and run it only when that Enumerator is
|
|
280
|
+
# consumed, so the block may run after locals it reads or writes have changed. They are NOT
|
|
281
|
+
# non-escaping. `ENUMERABLE_NON_ESCAPING` still lists them for the collection entries — a defect tracked
|
|
282
|
+
# as #1311; the stream entries below leave them out rather than inherit it.
|
|
283
|
+
DEFERRED_ENUMERATOR_METHODS = %i[chunk chunk_while slice_when slice_before slice_after].freeze
|
|
284
|
+
STREAM_ENUMERABLE_NON_ESCAPING = (ENUMERABLE_NON_ESCAPING - DEFERRED_ENUMERATOR_METHODS).freeze
|
|
285
|
+
|
|
157
286
|
NON_ESCAPING = {
|
|
158
287
|
"Array" => (ENUMERABLE_NON_ESCAPING + ARRAY_EXTRA).freeze,
|
|
159
288
|
"Hash" => (ENUMERABLE_NON_ESCAPING + HASH_EXTRA).freeze,
|
|
@@ -162,11 +291,24 @@ module Rigor
|
|
|
162
291
|
"Integer" => INTEGER_EXTRA,
|
|
163
292
|
"Enumerator" => ENUMERABLE_NON_ESCAPING,
|
|
164
293
|
"Enumerator::Lazy" => ENUMERABLE_NON_ESCAPING,
|
|
165
|
-
"IO" => (IO_ITERATION
|
|
166
|
-
"File" => (IO_ITERATION
|
|
167
|
-
"StringIO" => IO_ITERATION
|
|
294
|
+
"IO" => (STREAM_ENUMERABLE_NON_ESCAPING | IO_ITERATION | IO_SINGLETON_ITERATION).freeze,
|
|
295
|
+
"File" => (STREAM_ENUMERABLE_NON_ESCAPING | IO_ITERATION | IO_SINGLETON_ITERATION).freeze,
|
|
296
|
+
"StringIO" => (STREAM_ENUMERABLE_NON_ESCAPING | IO_ITERATION).freeze
|
|
297
|
+
}.freeze
|
|
298
|
+
|
|
299
|
+
# Issue #1234 — the catalogued modules a project class reaches only through its ancestry, read only by
|
|
300
|
+
# {.ancestry_non_escaping?} after the project has been ruled out as the method's owner. `Enumerable`'s
|
|
301
|
+
# methods run the block from inside the call, through the includer's `each` — minus
|
|
302
|
+
# {DEFERRED_ENUMERATOR_METHODS}, whose Enumerator outlives it, and minus `each` itself, which
|
|
303
|
+
# `Enumerable` does not declare: the includer supplies it, so nothing here speaks for it. For that
|
|
304
|
+
# reason a receiver typed as the bare module is not a key of {NON_ESCAPING} either.
|
|
305
|
+
MIXIN_NON_ESCAPING = {
|
|
306
|
+
"Enumerable" => (STREAM_ENUMERABLE_NON_ESCAPING - %i[each]).freeze
|
|
168
307
|
}.freeze
|
|
169
308
|
|
|
309
|
+
# Every name a {NON_ESCAPING} entry lists as iteration, for {.iterator_name?} and the ancestry gate.
|
|
310
|
+
ITERATOR_NAMES = (NON_ESCAPING.values.flatten.to_set - OBJECT_NON_ESCAPING).freeze
|
|
311
|
+
|
|
170
312
|
# Methods that are documented to **retain** the block past the call. The block is stored or scheduled,
|
|
171
313
|
# so outer narrowing facts on writeable captured locals cannot survive.
|
|
172
314
|
ESCAPING = {
|
|
@@ -35,23 +35,28 @@ module Rigor
|
|
|
35
35
|
# argument and the value the last.
|
|
36
36
|
HASH_CONTENT_ADDERS = %i[[]= store].to_set.freeze
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
38
|
+
# Every method name that adds to a collection's CONTENT — the evidence the joins read, and the
|
|
39
|
+
# gate the straight-line path types its arguments behind. A String carries no element
|
|
40
|
+
# parameter, so it has no adders of its own; `StatementEvaluator::CONTENT_MUTATORS` adds the
|
|
41
|
+
# String mutators to what its scans count.
|
|
42
|
+
CONTENT_ADDERS = (ARRAY_CONTENT_ADDERS | HASH_CONTENT_ADDERS).freeze
|
|
43
43
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
|
|
44
|
+
# The probes {#could_be_range?} passes to `accepts` — interned because the questions are
|
|
45
|
+
# asked once per `[]=` index member.
|
|
46
|
+
RANGE_INDEX_PROBE = Type::Combinator.nominal_of("Range")
|
|
47
|
+
OBJECT_INDEX_PROBE = Type::Combinator.nominal_of("Object")
|
|
48
|
+
ARRAY_PROBE = Type::Combinator.nominal_of("Array")
|
|
49
|
+
private_constant :RANGE_INDEX_PROBE, :OBJECT_INDEX_PROBE, :ARRAY_PROBE
|
|
47
50
|
|
|
48
51
|
module_function
|
|
49
52
|
|
|
50
53
|
# The element types a single content-mutator call introduces into an Array, given the
|
|
51
54
|
# per-argument types (already typed by the caller in the scope the arguments are evaluated
|
|
52
55
|
# in). `concat`/`replace` take collection arguments, so their element evidence is the
|
|
53
|
-
# arguments' OWN element types unioned; the
|
|
54
|
-
# `[]`
|
|
56
|
+
# arguments' OWN element types unioned; the `[]=` splice forms (`arr[i, n] = other` /
|
|
57
|
+
# `arr[range] = other`) store their value's elements the same way and read it through the
|
|
58
|
+
# same unwrap (issue #1140). The rest append the argument values directly. Returns `[]` when
|
|
59
|
+
# there is no element evidence (e.g. a `<<` with no resolvable arg).
|
|
55
60
|
def array_added_elements(method_name, arg_types)
|
|
56
61
|
return [] if arg_types.empty?
|
|
57
62
|
|
|
@@ -62,21 +67,27 @@ module Rigor
|
|
|
62
67
|
# `insert(index, *objs)` — first arg is the position.
|
|
63
68
|
arg_types.drop(1)
|
|
64
69
|
when :[]=
|
|
65
|
-
# `arr[i] = v`
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
[arg_types.last]
|
|
70
|
+
# `arr[i] = v` stores `v` as ONE element — the index arguments precede the value.
|
|
71
|
+
# The splice forms store the value's ELEMENTS instead (issue #1140): `a[0, 2] = [1, 2]`
|
|
72
|
+
# puts `Integer`s in the receiver, not an `Array[Integer]`. An index the engine cannot
|
|
73
|
+
# classify (`a[x] = v` with `x` untyped, a `Range | Integer` union, or a splat that
|
|
74
|
+
# may vanish inside the brackets) may be either form, so both readings join — the
|
|
75
|
+
# union is a superset of the truth either way.
|
|
76
|
+
case index_store_form(arg_types)
|
|
77
|
+
when :splice then splice_stored_elements(arg_types.last)
|
|
78
|
+
when :either then [arg_types.last] + splice_stored_elements(arg_types.last)
|
|
79
|
+
else [arg_types.last]
|
|
80
|
+
end
|
|
74
81
|
when :fill
|
|
75
82
|
# `fill(value)` — only the no-block single-value form adds a concrete element; block /
|
|
76
83
|
# range forms are conservatively ignored (the arity-forget already widened the binding).
|
|
77
84
|
arg_types.size == 1 ? arg_types : []
|
|
78
|
-
|
|
85
|
+
when :<<, :push, :append, :prepend, :unshift
|
|
79
86
|
arg_types
|
|
87
|
+
else
|
|
88
|
+
# Not an Array adder — a String mutator a content scan counted (`x.sub!("a", "b")` on an
|
|
89
|
+
# `Array | String` capture): its arguments are not elements.
|
|
90
|
+
[]
|
|
80
91
|
end
|
|
81
92
|
end
|
|
82
93
|
|
|
@@ -122,6 +133,23 @@ module Rigor
|
|
|
122
133
|
with_residue(Type::Combinator.nominal_of("Hash", type_args: [key_t, value_t]), hash_residue(pre_state))
|
|
123
134
|
end
|
|
124
135
|
|
|
136
|
+
# Builds the continuation of a pre-state that carries BOTH an Array and a Hash member, from the
|
|
137
|
+
# pairs and the elements the body stores. Each side is joined with its own class's evidence, and
|
|
138
|
+
# every member neither side absorbs survives beside the Hash carrier ({#hash_residue}).
|
|
139
|
+
#
|
|
140
|
+
# Joined as one carrier, the other class's member was residue and survived exactly as the seed
|
|
141
|
+
# had it: an unwidened literal `Tuple` / `HashShape`, so what the body did to it was gone — `x =
|
|
142
|
+
# gets ? [1] : { a: 1 }; [0].each { x.map!(&:to_s) if x.is_a?(Array); x[:k] = 2 if
|
|
143
|
+
# x.is_a?(Hash) }` read `[1]`, and `x.first.upcase` drew `undefined method` on correct code. The
|
|
144
|
+
# caller routes the evidence: a store may reach either member, and only the caller sees its
|
|
145
|
+
# index (`StatementEvaluator#mixed_content_evidence`).
|
|
146
|
+
def join_mixed_content(pre_state, added_pairs, added_elements)
|
|
147
|
+
members = pre_state.is_a?(Type::Union) ? pre_state.members : [pre_state]
|
|
148
|
+
arrays, rest = members.partition { |member| array_residue(member).empty? }
|
|
149
|
+
array = join_array_content(Type::Combinator.union(*arrays), added_elements)
|
|
150
|
+
Type::Combinator.union(array, join_hash_content(Type::Combinator.union(*rest), added_pairs))
|
|
151
|
+
end
|
|
152
|
+
|
|
125
153
|
# ----------------------------------------------------------------
|
|
126
154
|
# Union residue — the members the rederived carrier does NOT stand for (issue #631).
|
|
127
155
|
#
|
|
@@ -277,15 +305,158 @@ module Rigor
|
|
|
277
305
|
type.is_a?(Type::Union) ? type.members : [type]
|
|
278
306
|
end
|
|
279
307
|
|
|
308
|
+
# How an `arr[...] = v` call's index arguments place the stored value: `:splice` — the
|
|
309
|
+
# value's ELEMENTS land in the receiver (`arr[i, n] = v` takes two index slots,
|
|
310
|
+
# `arr[range] = v` takes a Range); `:element` — the value itself is one stored element
|
|
311
|
+
# (`arr[i] = v`); `:either` — the store may be either form.
|
|
312
|
+
#
|
|
313
|
+
# A splat inside the brackets is marked `nil` by the caller — its expansion can change the
|
|
314
|
+
# store's arity (`a[0, *xs] = v` stores `v` itself when `xs` is empty and `v`'s elements
|
|
315
|
+
# otherwise). An ordinary argument counts as a provable index however untyped it is:
|
|
316
|
+
# `a[i, n] = v` is a splice at every binding of `i` and `n`. Two or more provable indices
|
|
317
|
+
# are therefore always a splice however a splat expands; one provable index beside a splat
|
|
318
|
+
# reduces to the Range question (extra expansion only reaches an arity error, never a
|
|
319
|
+
# scalar store); zero provable indices leaves the form to whatever the splat yields.
|
|
320
|
+
def index_store_form(arg_types)
|
|
321
|
+
leading = arg_types[0...-1]
|
|
322
|
+
definite = leading.compact
|
|
323
|
+
return :splice if definite.size >= 2
|
|
324
|
+
return :either if definite.empty?
|
|
325
|
+
return single_index_form(definite.first) if definite.size == leading.size
|
|
326
|
+
|
|
327
|
+
range_index?(definite.first) ? :splice : :either
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# The one-index question: a Range carrier is a splice, a definite scalar an element store,
|
|
331
|
+
# and anything between — a union straddling both, a broad index that may still hold a
|
|
332
|
+
# Range at runtime — may be either.
|
|
333
|
+
def single_index_form(index_type)
|
|
334
|
+
members = union_members(index_type)
|
|
335
|
+
return :splice if members.all? { |m| definite_range?(m) }
|
|
336
|
+
return :element if members.none? { |m| could_be_range?(m) }
|
|
337
|
+
|
|
338
|
+
:either
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# True when the index position PROVABLY holds a Range: a Range carrier {#range_index?}
|
|
342
|
+
# recognizes, or a member acceptance proves a subtype of `Range` — a loaded
|
|
343
|
+
# `class MyRange < Range` answers `yes` there where the name check cannot see it. An
|
|
344
|
+
# unresolvable subclass answers `maybe`, which correctly declines here and still reaches
|
|
345
|
+
# {#could_be_range?}'s `either` reading. A gradual member accepts in BOTH directions
|
|
346
|
+
# (`Range` accepts it optimistically), so it must be excluded before asking — an untyped
|
|
347
|
+
# index may hold a scalar just as well and stays `either`. The exclusion reads through a
|
|
348
|
+
# `Difference` / `Refined` to its base: acceptance projects the wrapper onto the base too,
|
|
349
|
+
# so a `non-nil` or refined `Dynamic` answers `yes` there exactly like the bare gradual
|
|
350
|
+
# while still being free to hold a scalar at runtime.
|
|
351
|
+
def definite_range?(member)
|
|
352
|
+
return true if range_index?(member)
|
|
353
|
+
|
|
354
|
+
case member
|
|
355
|
+
when Type::Dynamic, Type::Top then false
|
|
356
|
+
when Type::Difference, Type::Refined then definite_range?(member.base)
|
|
357
|
+
else member.respond_to?(:accepts) && RANGE_INDEX_PROBE.accepts(member).yes?
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# True when the index position MAY hold a Range at runtime: a definite Range carrier
|
|
362
|
+
# (`Constant[0..1]` accepts only its own value, so acceptance alone cannot see it),
|
|
363
|
+
# `Dynamic`, a carrier the engine cannot answer for, and every type related to
|
|
364
|
+
# `Nominal[Range]` in EITHER direction — `Object`/`Enumerable`/`top` above it, a
|
|
365
|
+
# project `class MyRange < Range` below it (an unresolvable class name answers `maybe`,
|
|
366
|
+
# never `no`, so subclass-ness cannot be ruled out).
|
|
367
|
+
#
|
|
368
|
+
# `no` in both directions is a real disjointness only when the member's values are
|
|
369
|
+
# provably `Object` instances — a fixed-class value set that single inheritance keeps
|
|
370
|
+
# from ever being a Range (`Integer`, a non-Range `Constant`, a `Tuple`). A MODULE
|
|
371
|
+
# member fails that probe (`Comparable`'s ancestors exclude `Object` — a module has no
|
|
372
|
+
# superclass chain), and rightly so: `class MyRange < Range; include Comparable; end`
|
|
373
|
+
# satisfies the annotation and still splices at runtime.
|
|
374
|
+
def could_be_range?(member)
|
|
375
|
+
return true if range_index?(member)
|
|
376
|
+
|
|
377
|
+
case member
|
|
378
|
+
when Type::Intersection
|
|
379
|
+
# An intersected value sits in EVERY member's value set, so it can be a Range only
|
|
380
|
+
# when every member allows one — `String & Comparable` cannot be (a String never
|
|
381
|
+
# is), `Object & Comparable` can (`MyRange < Range; include Comparable` satisfies
|
|
382
|
+
# both). Asked member-wise because the `Object` probe below answers yes when ANY
|
|
383
|
+
# intersected member accepts it.
|
|
384
|
+
member.members.all? { |part| could_be_range?(part) }
|
|
385
|
+
when Type::Maybe then could_be_range?(member.value_type)
|
|
386
|
+
else
|
|
387
|
+
return true unless member.respond_to?(:accepts)
|
|
388
|
+
return true unless member.accepts(RANGE_INDEX_PROBE).no? &&
|
|
389
|
+
RANGE_INDEX_PROBE.accepts(member).no?
|
|
390
|
+
|
|
391
|
+
!OBJECT_INDEX_PROBE.accepts(member).yes?
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Element types a splice RHS adds to the receiver, read member-wise. Ruby splices an
|
|
396
|
+
# Array RHS (`a[0, 1] = [1, 2]` puts `Integer`s in — a bare `Nominal[Array]` splices
|
|
397
|
+
# elements the signature left untyped, which are unknown rather than none), and stores a
|
|
398
|
+
# NON-Array RHS as one element UNLESS the value coerces through `to_ary`, in which case
|
|
399
|
+
# the returned array's elements splice instead (`a[0, 1] = obj` where `obj.to_ary`
|
|
400
|
+
# returns `["x"]` stores `"x"`). `nil` is no exception — `a[i, n] = nil` stores a single
|
|
401
|
+
# nil.
|
|
402
|
+
#
|
|
403
|
+
# Every member that is not an Array carrier keeps a `Dynamic[top]` arm beside itself:
|
|
404
|
+
# `to_ary` is consulted for a NON-Array value, and nothing here can prove the member's
|
|
405
|
+
# class does not define one — a `Nominal` may be a subclass that declares it, and the
|
|
406
|
+
# project may reopen even a non-subclassable core class (ADR-17's patched-methods tier
|
|
407
|
+
# resolves exactly those). The two exact carriers are the ones `Array#[]=` splices
|
|
408
|
+
# without a coercion: a `Tuple` literal's elements, and a `Nominal[Array]`'s own type
|
|
409
|
+
# arguments — an Array object is spliced directly even when its class overrides
|
|
410
|
+
# `to_ary`.
|
|
411
|
+
def splice_stored_elements(value_type)
|
|
412
|
+
union_members(value_type).flat_map do |member|
|
|
413
|
+
base = member
|
|
414
|
+
base = base.base while base.is_a?(Type::Difference) || base.is_a?(Type::Refined)
|
|
415
|
+
|
|
416
|
+
case base
|
|
417
|
+
when Type::Tuple
|
|
418
|
+
base.elements
|
|
419
|
+
when Type::Nominal
|
|
420
|
+
if base.class_name == "Array"
|
|
421
|
+
# `Array#[]=` splices an Array RHS directly — `to_ary` is never consulted
|
|
422
|
+
# for an Array object, even a subclass overriding it — so the elements
|
|
423
|
+
# join exactly (a bare `Array` signature left them unknown, not absent).
|
|
424
|
+
base.type_args.empty? ? [Type::Combinator.untyped] : base.type_args
|
|
425
|
+
elsif ARRAY_PROBE.accepts(base).yes?
|
|
426
|
+
# A definite Array subclass is spliced the same way; its elements are
|
|
427
|
+
# unknown to this seam.
|
|
428
|
+
[Type::Combinator.untyped]
|
|
429
|
+
else
|
|
430
|
+
[member, Type::Combinator.untyped]
|
|
431
|
+
end
|
|
432
|
+
else
|
|
433
|
+
[member, Type::Combinator.untyped]
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# True when an index position holds a Range carrier — a static `Constant<Range>` (`0..1`),
|
|
439
|
+
# a `Nominal[Range]` (`(0..n)`), or a refinement / difference over either. `IntegerRange`
|
|
440
|
+
# and `FloatRange` are scalar number refinements rather than Range objects and correctly
|
|
441
|
+
# decline, as does every ordinary scalar index.
|
|
442
|
+
def range_index?(type)
|
|
443
|
+
case type
|
|
444
|
+
when Type::Constant then type.value.is_a?(::Range)
|
|
445
|
+
when Type::Nominal then type.class_name == "Range"
|
|
446
|
+
when Type::Difference, Type::Refined then range_index?(type.base)
|
|
447
|
+
else false
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
280
451
|
# Element types carried by a collection binding, regardless of which carrier holds them: a
|
|
281
452
|
# `Tuple` lists them, a `Nominal[Array, [E]]` has one element param, a bare `Array` /
|
|
282
453
|
# anything else yields none.
|
|
283
454
|
#
|
|
284
455
|
# A `Difference` reads through to its base: `non-empty-array[T]` holds `T`s, and the seams
|
|
285
456
|
# that read a seed from BEFORE the arity-forget ran (see {#join_array_content}) meet the
|
|
286
|
-
# refinement carrier itself where they used to meet the base the widening had left.
|
|
287
|
-
# it there would hand the continuation the widened base ALONE, with every appended
|
|
288
|
-
# — a wrong type, not a wide one.
|
|
457
|
+
# refinement carrier itself where they used to meet the base the widening had left.
|
|
458
|
+
# Declining it there would hand the continuation the widened base ALONE, with every appended
|
|
459
|
+
# arm missing — a wrong type, not a wide one.
|
|
289
460
|
def collection_element_types(type)
|
|
290
461
|
case type
|
|
291
462
|
when Type::Tuple
|
|
@@ -305,13 +476,15 @@ module Rigor
|
|
|
305
476
|
|
|
306
477
|
# `[keys, values]` evidence from a Hash-ish pre-state binding — a `HashShape` (literal pairs)
|
|
307
478
|
# or a `Nominal[Hash, [K, V]]`. A `Difference` (`non-empty-hash[K, V]`) reads through to its
|
|
308
|
-
# base, as {#collection_element_types} does for the Array side.
|
|
479
|
+
# base, as {#collection_element_types} does for the Array side. An open shape adds a
|
|
480
|
+
# `Dynamic[top]` key and value: its unseen keys may hold anything, and the joined carrier's
|
|
481
|
+
# read of one must not answer a known value (`flags = { debug: true }; flags.default = false`
|
|
482
|
+
# then a store in a block read `flags[:verbose]` as `true`).
|
|
309
483
|
def hash_shape_key_values(type)
|
|
310
484
|
case type
|
|
311
485
|
when Type::HashShape
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
[[key_union_for(type.pairs.keys)], type.pairs.values]
|
|
486
|
+
keys, values = type.pairs.empty? ? [[], []] : [[key_union_for(type.pairs.keys)], type.pairs.values]
|
|
487
|
+
type.open? ? [keys + [Type::Combinator.untyped], values + [Type::Combinator.untyped]] : [keys, values]
|
|
315
488
|
when Type::Nominal
|
|
316
489
|
type.class_name == "Hash" && type.type_args.size == 2 ? [[type.type_args[0]], [type.type_args[1]]] : [[], []]
|
|
317
490
|
when Type::Union
|
|
@@ -22,17 +22,17 @@ module Rigor
|
|
|
22
22
|
# a tracer per visited node and discards the inferred type values.
|
|
23
23
|
class CoverageScanner
|
|
24
24
|
class Result < Data.define(:visits, :unrecognized, :events)
|
|
25
|
-
# @return
|
|
25
|
+
# @return sum of all visits across node classes.
|
|
26
26
|
def visited_count
|
|
27
27
|
visits.values.sum
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
# @return
|
|
30
|
+
# @return sum of directly-unrecognized counts across classes.
|
|
31
31
|
def unrecognized_count
|
|
32
32
|
unrecognized.values.sum
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# @return
|
|
35
|
+
# @return unrecognized_count / visited_count, or 0.0 when empty.
|
|
36
36
|
def unrecognized_ratio
|
|
37
37
|
total = visited_count
|
|
38
38
|
return 0.0 if total.zero?
|
|
@@ -41,13 +41,11 @@ module Rigor
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
# @param scope
|
|
44
|
+
# @param scope — base scope used for every type_of call. Defaults to `Scope.empty`.
|
|
45
45
|
def initialize(scope: nil)
|
|
46
46
|
@scope = scope || Scope.empty
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
# @param root [Prism::Node]
|
|
50
|
-
# @return [Result]
|
|
51
49
|
def scan(root)
|
|
52
50
|
visits = Hash.new(0)
|
|
53
51
|
unrecognized = Hash.new(0)
|
|
@@ -4,6 +4,7 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../type"
|
|
6
6
|
require_relative "../source/node_children"
|
|
7
|
+
require_relative "return_barrier"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
9
10
|
module Inference
|
|
@@ -13,15 +14,14 @@ module Rigor
|
|
|
13
14
|
# The inferred type is the union of:
|
|
14
15
|
#
|
|
15
16
|
# - the body's last-statement type, and
|
|
16
|
-
# - the type of every explicit `return value`
|
|
17
|
-
#
|
|
17
|
+
# - the type of every explicit `return value` that exits the method. A `return` inside an ordinary block does
|
|
18
|
+
# (control-flow-analysis.md § "Non-local exits"); one inside a {ReturnBarrier} — a nested `def`, a lambda,
|
|
19
|
+
# or a `define_method` body — does not. The barriers are the ones `StatementEvaluator`'s return sink stops
|
|
20
|
+
# at, so the proposal and `rigor check`'s view of callers agree (issue #1382).
|
|
18
21
|
#
|
|
19
22
|
# Extracted from `Rigor::SigGen::Generator#infer_return_type` so `LineTypeCollector` (`rigor annotate`'s
|
|
20
23
|
# def-line annotator) and the sig-generator share one source of truth.
|
|
21
24
|
module DefReturnTyper
|
|
22
|
-
RETURN_BARRIER_NODES = [Prism::DefNode, Prism::LambdaNode, Prism::BlockNode].freeze
|
|
23
|
-
private_constant :RETURN_BARRIER_NODES
|
|
24
|
-
|
|
25
25
|
module_function
|
|
26
26
|
|
|
27
27
|
def call(def_node, scope_index)
|
|
@@ -60,10 +60,14 @@ module Rigor
|
|
|
60
60
|
|
|
61
61
|
def collect_return_types(node, scope_index, out)
|
|
62
62
|
return unless node.is_a?(Prism::Node)
|
|
63
|
-
return if
|
|
63
|
+
return if ReturnBarrier.node?(node)
|
|
64
64
|
|
|
65
65
|
type_return_node(node, scope_index, out) if node.is_a?(Prism::ReturnNode)
|
|
66
|
-
|
|
66
|
+
# A barrier call's receiver and arguments still run in the method; only its block is cut off.
|
|
67
|
+
barrier_block = node.block if node.is_a?(Prism::CallNode) && ReturnBarrier.block_call?(node)
|
|
68
|
+
node.rigor_each_child do |c|
|
|
69
|
+
collect_return_types(c, scope_index, out) unless c.equal?(barrier_block)
|
|
70
|
+
end
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
def type_return_node(return_node, scope_index, out)
|