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,447 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../source/node_children"
|
|
6
|
+
require_relative "../type"
|
|
7
|
+
require_relative "external_ancestor_resolution"
|
|
8
|
+
require_relative "fresh_frame_blocks"
|
|
9
|
+
require_relative "project_method_ownership"
|
|
10
|
+
require_relative "stored_block_call"
|
|
11
|
+
|
|
12
|
+
module Rigor
|
|
13
|
+
module Inference
|
|
14
|
+
# Issue #1429 — where a guard's narrowing of a global or constant stops holding. A guard narrows `$stdout` or
|
|
15
|
+
# `STDOUT` on its edge ({Narrowing}), but any Ruby code that runs between the guard and a read may rebind the
|
|
16
|
+
# global (`$stdout = StringIO.new`) or the constant (`const_set`), and the analysis cannot see that code. So the
|
|
17
|
+
# statement evaluator restores each narrowed name to the union of its pre-guard binding and its narrowed one
|
|
18
|
+
# ({Scope#forget_guard_narrowings}) wherever code this module counts may run:
|
|
19
|
+
#
|
|
20
|
+
# - a call that may run project, gem or unresolved code ({.call_runs_foreign_code?}): a method the project
|
|
21
|
+
# defines on the receiver's class or its ancestry, one whose signature is owned outside Ruby core and the
|
|
22
|
+
# standard library, a core method on a project or gem receiver (`Enumerable#map` runs the class's `each`),
|
|
23
|
+
# an unresolved callee (a `Dynamic` receiver, a name no signature declares), a call that runs code by name
|
|
24
|
+
# (`send`, `instance_eval`, `eval`, `require`, `load`), a call on a code object (`Proc`, `Method`,
|
|
25
|
+
# `Enumerator`, `Fiber`, `Thread`, a delegator), and a block-pass argument (`&blk`);
|
|
26
|
+
# - a literal block whose body may do either, or writes a global or constant itself;
|
|
27
|
+
# - `yield` and `super`, which run code the method does not show.
|
|
28
|
+
#
|
|
29
|
+
# A core or standard-library method on a core or standard-library receiver keeps the narrowing (`$sep.strip`,
|
|
30
|
+
# `$stdout.flush`, `File.read(path)`), and so does a method `Kernel`, `Object` or `BasicObject` owns on any
|
|
31
|
+
# receiver (`puts`, `format`, `obj.frozen?`), so `if $sep; $sep.strip; $sep.length; end` keeps `$sep` non-nil.
|
|
32
|
+
#
|
|
33
|
+
# The accepted gap is implicit conversion: a core method that calls back into a project method the program
|
|
34
|
+
# does not spell (`puts obj` runs `obj.to_s`, `hash[obj]` runs `obj.hash`, `a.sort` runs `<=>`) is read as the
|
|
35
|
+
# core method alone.
|
|
36
|
+
module GuardRebinding
|
|
37
|
+
# Calls that run code chosen by name or by a String.
|
|
38
|
+
CODE_RUNNING_NAMES = Set[
|
|
39
|
+
:send, :__send__, :public_send, :eval, :require, :require_relative, :load,
|
|
40
|
+
# These rebind a constant on any receiver (`Object.const_set(:SEP, nil)`).
|
|
41
|
+
:const_set, :remove_const
|
|
42
|
+
].freeze
|
|
43
|
+
# These run their literal block, which the scan reads as any block, or a String of code, which it cannot read.
|
|
44
|
+
BLOCK_OR_CODE_NAMES = Set[
|
|
45
|
+
:instance_eval, :instance_exec, :class_eval, :class_exec, :module_eval, :module_exec
|
|
46
|
+
].freeze
|
|
47
|
+
# Methods `Kernel`, `Object` or `BasicObject` own that call another method of the receiver, which a project class
|
|
48
|
+
# may define: `r != 1` runs `r == 1`.
|
|
49
|
+
UNIVERSAL_DELEGATES = { :!= => :==, :!~ => :=~, :=== => :==, :respond_to? => :respond_to_missing? }.freeze
|
|
50
|
+
# Receivers whose methods run code the receiver holds: a block, a method, a generator or a delegate.
|
|
51
|
+
CODE_OBJECT_CLASSES = Set[
|
|
52
|
+
"Proc", "Method", "UnboundMethod", "Binding", "Enumerator", "Enumerator::Lazy", "Enumerator::Chain",
|
|
53
|
+
"Enumerator::Yielder", "Fiber", "Thread", "Delegator", "SimpleDelegator"
|
|
54
|
+
].freeze
|
|
55
|
+
# The owners whose methods a project object answers the same way any object does.
|
|
56
|
+
UNIVERSAL_OWNERS = Set["Kernel", "Object", "BasicObject"].freeze
|
|
57
|
+
# The nodes that write a global or a constant.
|
|
58
|
+
WRITE_NODES = [
|
|
59
|
+
Prism::GlobalVariableWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode,
|
|
60
|
+
Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableTargetNode,
|
|
61
|
+
Prism::ConstantWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantAndWriteNode,
|
|
62
|
+
Prism::ConstantOperatorWriteNode, Prism::ConstantTargetNode, Prism::ConstantPathWriteNode,
|
|
63
|
+
Prism::ConstantPathOrWriteNode, Prism::ConstantPathAndWriteNode, Prism::ConstantPathOperatorWriteNode,
|
|
64
|
+
Prism::ConstantPathTargetNode
|
|
65
|
+
].freeze
|
|
66
|
+
# Code that runs code the method does not show.
|
|
67
|
+
FOREIGN_NODES = [Prism::YieldNode, Prism::SuperNode, Prism::ForwardingSuperNode].freeze
|
|
68
|
+
REBINDING_NODES = (WRITE_NODES + FOREIGN_NODES).to_set.freeze
|
|
69
|
+
# Nodes that call methods the syntax does not spell as a `CallNode`: an operator write calls its operator (`r +=
|
|
70
|
+
# 1` calls `r.+`), an attribute or index compound write calls the reader and the writer (`r.val ||= 1`, `r[0] +=
|
|
71
|
+
# 1`), and a `for` loop calls `each` on its collection.
|
|
72
|
+
IMPLICIT_CALL_NODES = Set[
|
|
73
|
+
Prism::LocalVariableOperatorWriteNode, Prism::InstanceVariableOperatorWriteNode,
|
|
74
|
+
Prism::ClassVariableOperatorWriteNode, Prism::CallOperatorWriteNode, Prism::CallOrWriteNode,
|
|
75
|
+
Prism::CallAndWriteNode, Prism::IndexOperatorWriteNode, Prism::IndexOrWriteNode, Prism::IndexAndWriteNode,
|
|
76
|
+
Prism::ForNode
|
|
77
|
+
].freeze
|
|
78
|
+
VARIABLE_OPERATOR_WRITES = {
|
|
79
|
+
Prism::LocalVariableOperatorWriteNode => :local, Prism::InstanceVariableOperatorWriteNode => :ivar,
|
|
80
|
+
Prism::ClassVariableOperatorWriteNode => :cvar
|
|
81
|
+
}.freeze
|
|
82
|
+
# Calls that keep their literal block to run at a later event, after the method returns or between any two of
|
|
83
|
+
# its statements: an exit handler, a signal handler, a global-assignment hook, a trace hook and a finalizer.
|
|
84
|
+
# `nil` keys the Kernel function's spellings (`at_exit`, `self.trap`, `Kernel.at_exit`); a constant keys its
|
|
85
|
+
# singleton methods.
|
|
86
|
+
DEFERRED_BLOCK_CALLS = {
|
|
87
|
+
nil => %i[at_exit trap trace_var], Signal: %i[trap], TracePoint: %i[new trace],
|
|
88
|
+
ObjectSpace: %i[define_finalizer]
|
|
89
|
+
}.transform_values(&:freeze).freeze
|
|
90
|
+
private_constant :CODE_RUNNING_NAMES, :BLOCK_OR_CODE_NAMES, :UNIVERSAL_DELEGATES, :CODE_OBJECT_CLASSES,
|
|
91
|
+
:UNIVERSAL_OWNERS, :WRITE_NODES, :FOREIGN_NODES,
|
|
92
|
+
:REBINDING_NODES, :IMPLICIT_CALL_NODES, :VARIABLE_OPERATOR_WRITES, :DEFERRED_BLOCK_CALLS
|
|
93
|
+
|
|
94
|
+
module_function
|
|
95
|
+
|
|
96
|
+
# True when `call_node`, a statement's own call, may rebind a global or constant once its operands ran: its
|
|
97
|
+
# method may run foreign code, or its literal block may.
|
|
98
|
+
def call_may_rebind?(call_node, scope)
|
|
99
|
+
return true unless call_node.is_a?(Prism::CallNode)
|
|
100
|
+
return true if call_runs_foreign_code?(call_node, scope)
|
|
101
|
+
|
|
102
|
+
block = call_node.block
|
|
103
|
+
block.is_a?(Prism::BlockNode) && may_rebind?(block, ScanScope.block_parameter_scope(call_node, block, scope))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# True when the receiver chain or the arguments of `call_node` may rebind one; Ruby runs them before the call.
|
|
107
|
+
def operands_may_rebind?(call_node, scope)
|
|
108
|
+
may_rebind?(call_node.receiver, scope) || may_rebind?(call_node.arguments, scope) ||
|
|
109
|
+
(call_node.block.is_a?(Prism::BlockArgumentNode) && may_rebind?(call_node.block, scope))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# True when running `node` may rebind one: it writes a global or constant, yields, calls `super`, or holds a
|
|
113
|
+
# call, spelled or implicit ({.implicit_call_may_rebind?}), whose method may run foreign code. A `def` and a
|
|
114
|
+
# lambda literal run nothing where they are written. Each node is visited once: a call's literal block is
|
|
115
|
+
# reached as one of its children, so a nested block chain costs its size, not its depth's power.
|
|
116
|
+
#
|
|
117
|
+
# A local the scanned code writes has no binding in `scope` yet, so the scan reads it as the code writes it
|
|
118
|
+
# ({ScanScope.with_scanned_locals}): `copy = $sep; copy.length` is a `String` call. A literal block's parameters
|
|
119
|
+
# read as the method's signature yields them ({ScanScope.block_parameter_scope}).
|
|
120
|
+
def may_rebind?(node, scope)
|
|
121
|
+
return false unless node.is_a?(Prism::Node)
|
|
122
|
+
|
|
123
|
+
scan(node, ScanScope.with_scanned_locals(node, scope))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def scan(node, scope)
|
|
127
|
+
return false unless node.is_a?(Prism::Node)
|
|
128
|
+
return true if REBINDING_NODES.include?(node.class)
|
|
129
|
+
return false if node.is_a?(Prism::DefNode) || node.is_a?(Prism::LambdaNode)
|
|
130
|
+
return scan_call(node, scope) if node.is_a?(Prism::CallNode)
|
|
131
|
+
return true if IMPLICIT_CALL_NODES.include?(node.class) && implicit_call_may_rebind?(node, scope)
|
|
132
|
+
|
|
133
|
+
found = false
|
|
134
|
+
node.rigor_each_child { |child| found ||= scan(child, scope) }
|
|
135
|
+
found
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def scan_call(node, scope)
|
|
139
|
+
return true if call_runs_foreign_code?(node, scope)
|
|
140
|
+
return true if scan(node.receiver, scope) || scan(node.arguments, scope)
|
|
141
|
+
|
|
142
|
+
block = node.block
|
|
143
|
+
return scan(block, scope) unless block.is_a?(Prism::BlockNode)
|
|
144
|
+
|
|
145
|
+
scan(block, ScanScope.block_scope(node, block, scope))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# True when `node` is a compound write or `for` loop that calls a method its syntax does not spell.
|
|
149
|
+
def implicit_call_node?(node)
|
|
150
|
+
IMPLICIT_CALL_NODES.include?(node.class)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# True when the method a compound write or a `for` loop calls without spelling it may run foreign code
|
|
154
|
+
# ({IMPLICIT_CALL_NODES}). The operator of an attribute or index operator write runs on the value the reader
|
|
155
|
+
# returns, typed through the dispatcher; one it cannot type counts.
|
|
156
|
+
def implicit_call_may_rebind?(node, scope)
|
|
157
|
+
kind = VARIABLE_OPERATOR_WRITES[node.class]
|
|
158
|
+
return type_method_foreign?(variable_type(kind, node.name, scope), node.binary_operator, scope) if kind
|
|
159
|
+
return type_method_foreign?(scope.type_of(node.collection), :each, scope) if node.is_a?(Prism::ForNode)
|
|
160
|
+
|
|
161
|
+
compound_write_foreign?(node, scope)
|
|
162
|
+
rescue StandardError
|
|
163
|
+
true
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def compound_write_foreign?(node, scope)
|
|
167
|
+
receiver_type = compound_receiver_type(node, scope)
|
|
168
|
+
reader, writer = compound_accessors(node)
|
|
169
|
+
return true if [reader, writer].any? { |name| type_method_foreign?(receiver_type, name, scope) }
|
|
170
|
+
return false unless node.respond_to?(:binary_operator)
|
|
171
|
+
|
|
172
|
+
read = compound_read_type(node, receiver_type, reader, scope)
|
|
173
|
+
read.nil? || type_method_foreign?(read, node.binary_operator, scope)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def compound_receiver_type(node, scope)
|
|
177
|
+
return scope.type_of(node.receiver) if node.receiver
|
|
178
|
+
|
|
179
|
+
scope.self_type || Type::Combinator.nominal_of("Object")
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def compound_accessors(node)
|
|
183
|
+
return %i[[] []=] if node.respond_to?(:arguments) && !node.respond_to?(:read_name)
|
|
184
|
+
|
|
185
|
+
[node.read_name, node.write_name]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def compound_read_type(node, receiver_type, reader, scope)
|
|
189
|
+
arguments = node.respond_to?(:arguments) && node.arguments ? node.arguments.arguments : []
|
|
190
|
+
MethodDispatcher.dispatch(receiver_type: receiver_type, method_name: reader,
|
|
191
|
+
arg_types: arguments.map { |argument| scope.type_of(argument) },
|
|
192
|
+
environment: scope.environment, scope: scope)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def variable_type(kind, name, scope)
|
|
196
|
+
case kind
|
|
197
|
+
when :local then scope.local(name)
|
|
198
|
+
when :ivar then scope.ivar(name)
|
|
199
|
+
else scope.cvar(name)
|
|
200
|
+
end || Type::Combinator.untyped
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# True when `method_name` on a value of `type` may run foreign code ({.foreign_target?}).
|
|
204
|
+
def type_method_foreign?(type, method_name, scope)
|
|
205
|
+
targets = ProjectMethodOwnership.targets(type)
|
|
206
|
+
return true if targets.nil? || targets.empty?
|
|
207
|
+
|
|
208
|
+
targets.any? { |class_name, kind| foreign_target?(class_name, method_name, kind, scope) }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# The scope the body of `block_node` (a block, a lambda literal or an `END { }` body) enters with: `scope` with
|
|
212
|
+
# its guard narrowings restored where the body may run after code that rebinds them. A lambda, an `END` body,
|
|
213
|
+
# a block the call keeps to run later (`proc`, `define_method`) or at a later event ({DEFERRED_BLOCK_CALLS}),
|
|
214
|
+
# the root block of a thread or fiber, the block of a call that itself may run foreign code (`with_retry {
|
|
215
|
+
# $g.length }` runs after the helper's body), and a body that may rebind one itself (a later run reads what an
|
|
216
|
+
# earlier one wrote). A block with no owning call is left alone.
|
|
217
|
+
def block_entry(scope, block_node, call_node)
|
|
218
|
+
return scope unless scope.guard_narrowed?
|
|
219
|
+
return scope.forget_guard_narrowings if block_node.is_a?(Prism::LambdaNode) ||
|
|
220
|
+
block_node.is_a?(Prism::PostExecutionNode)
|
|
221
|
+
return scope unless call_node.is_a?(Prism::CallNode)
|
|
222
|
+
|
|
223
|
+
if StoredBlockCall.stores_block?(call_node) || deferred_block_call?(call_node) ||
|
|
224
|
+
FreshFrameBlocks.fresh_entry?(call_node, scope) || call_runs_foreign_code?(call_node, scope) ||
|
|
225
|
+
may_rebind?(block_node, ScanScope.block_parameter_scope(call_node, block_node, scope))
|
|
226
|
+
return scope.forget_guard_narrowings
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
scope
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# True when `call_node` is one of {DEFERRED_BLOCK_CALLS}, spelled on its literal receiver.
|
|
233
|
+
def deferred_block_call?(call_node)
|
|
234
|
+
receiver = call_node.receiver
|
|
235
|
+
key = StoredBlockCall.kernel_spelled?(receiver) ? nil : StoredBlockCall.root_constant_name(receiver)
|
|
236
|
+
names = DEFERRED_BLOCK_CALLS[key]
|
|
237
|
+
!names.nil? && names.include?(call_node.name)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# True when the method `call_node` calls may run code other than Ruby core and the standard library (see the
|
|
241
|
+
# module comment). Its operands and literal block are not read here.
|
|
242
|
+
def call_runs_foreign_code?(call_node, scope)
|
|
243
|
+
return true if call_node.block.is_a?(Prism::BlockArgumentNode)
|
|
244
|
+
return true if CODE_RUNNING_NAMES.include?(call_node.name)
|
|
245
|
+
return true if BLOCK_OR_CODE_NAMES.include?(call_node.name) &&
|
|
246
|
+
!(call_node.block.is_a?(Prism::BlockNode) && call_node.arguments.nil?)
|
|
247
|
+
|
|
248
|
+
targets = receiver_targets(call_node, scope)
|
|
249
|
+
return true if targets.nil? || targets.empty?
|
|
250
|
+
|
|
251
|
+
targets.any? { |class_name, kind| foreign_target?(class_name, call_node.name, kind, scope) }
|
|
252
|
+
rescue StandardError
|
|
253
|
+
true
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# The `[class_name, kind]` pairs the call dispatches on ({ProjectMethodOwnership.targets}); an implicit or
|
|
257
|
+
# `self.` receiver reads the scope's `self`, and the top level's `main` is an `Object`.
|
|
258
|
+
def receiver_targets(call_node, scope)
|
|
259
|
+
receiver = call_node.receiver
|
|
260
|
+
if receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
261
|
+
self_type = scope.self_type
|
|
262
|
+
return [["Object", :instance]] if self_type.nil?
|
|
263
|
+
|
|
264
|
+
return ProjectMethodOwnership.targets(self_type)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
ProjectMethodOwnership.targets(scope.type_of(receiver))
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def foreign_target?(class_name, method_name, kind, scope)
|
|
271
|
+
return true if CODE_OBJECT_CLASSES.include?(class_name)
|
|
272
|
+
return true if ProjectMethodOwnership.defines?(class_name, method_name, kind, scope)
|
|
273
|
+
|
|
274
|
+
owner = method_owner(class_name, method_name, kind, scope)
|
|
275
|
+
return true if owner.nil?
|
|
276
|
+
return universal_delegate_foreign?(class_name, method_name, kind, scope) if UNIVERSAL_OWNERS.include?(owner)
|
|
277
|
+
|
|
278
|
+
loader = scope.environment.rbs_loader
|
|
279
|
+
return true if loader.nil?
|
|
280
|
+
|
|
281
|
+
!(loader.core_or_stdlib_class?(owner) && loader.core_or_stdlib_class?(class_name))
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# A universal method is foreign only when it calls another method of the receiver the project defines
|
|
285
|
+
# ({UNIVERSAL_DELEGATES}).
|
|
286
|
+
def universal_delegate_foreign?(class_name, method_name, kind, scope)
|
|
287
|
+
delegate = UNIVERSAL_DELEGATES[method_name]
|
|
288
|
+
!delegate.nil? && ProjectMethodOwnership.defines?(class_name, delegate, kind, scope)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# The class or module whose signature answers `method_name` on `class_name`: its own RBS, an RBS ancestor of a
|
|
292
|
+
# project class ({ExternalAncestorResolution.resolve}), or `Object`'s for an instance method nothing earlier
|
|
293
|
+
# declares (a project class with no signature calling `puts`). nil when none does.
|
|
294
|
+
def method_owner(class_name, method_name, kind, scope)
|
|
295
|
+
definition = ExternalAncestorResolution.method_definition(class_name, method_name, kind, scope: scope)
|
|
296
|
+
if definition.nil? && kind == :instance
|
|
297
|
+
definition = ExternalAncestorResolution.resolve(class_name, method_name, :instance, scope: scope)&.first
|
|
298
|
+
definition ||= ExternalAncestorResolution.method_definition("Object", method_name, :instance, scope: scope)
|
|
299
|
+
end
|
|
300
|
+
owner = definition.respond_to?(:defined_in) ? definition.defined_in : nil
|
|
301
|
+
owner&.to_s&.delete_prefix("::")
|
|
302
|
+
end
|
|
303
|
+
private_class_method :scan, :scan_call, :receiver_targets, :foreign_target?, :universal_delegate_foreign?,
|
|
304
|
+
:method_owner, :deferred_block_call?,
|
|
305
|
+
:compound_write_foreign?,
|
|
306
|
+
:compound_receiver_type, :compound_accessors, :compound_read_type, :variable_type,
|
|
307
|
+
:type_method_foreign?
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
module GuardRebinding
|
|
311
|
+
# The scope {GuardRebinding.may_rebind?} types a scanned node's receivers in: the locals the scanned code writes
|
|
312
|
+
# and a literal block's parameters, which the scope before the code does not bind.
|
|
313
|
+
module ScanScope
|
|
314
|
+
LOCAL_SCAN_BARRIERS = [Prism::DefNode, Prism::LambdaNode, Prism::ClassNode, Prism::ModuleNode].freeze
|
|
315
|
+
private_constant :LOCAL_SCAN_BARRIERS
|
|
316
|
+
|
|
317
|
+
module_function
|
|
318
|
+
|
|
319
|
+
# `scope` with each local `node` writes read as the union of what it held and each value the code writes it,
|
|
320
|
+
# in source order, so a later write reads an earlier one. A local `scope` already binds keeps its binding in
|
|
321
|
+
# the union: the scanned code may run before or after its own write (a loop body, a block, a rescue clause).
|
|
322
|
+
# A nested `def`, lambda, class or module body is not read.
|
|
323
|
+
def with_scanned_locals(node, scope)
|
|
324
|
+
writes = []
|
|
325
|
+
collect_local_writes(node, writes)
|
|
326
|
+
writes.reduce(scope) do |acc, write|
|
|
327
|
+
written = acc.type_of(write.value)
|
|
328
|
+
existing = acc.local(write.name)
|
|
329
|
+
acc.with_local(write.name, existing ? Type::Combinator.union(existing, written) : written)
|
|
330
|
+
end
|
|
331
|
+
rescue StandardError
|
|
332
|
+
scope
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def collect_local_writes(node, writes)
|
|
336
|
+
return unless node.is_a?(Prism::Node)
|
|
337
|
+
return if LOCAL_SCAN_BARRIERS.any? { |barrier| node.is_a?(barrier) }
|
|
338
|
+
|
|
339
|
+
writes << node if node.is_a?(Prism::LocalVariableWriteNode)
|
|
340
|
+
node.rigor_each_child { |child| collect_local_writes(child, writes) }
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# The scope the body of `call_node`'s literal `block` is scanned in, from `scope`, the enclosing scan's: the
|
|
344
|
+
# block's parameters bound ({.block_parameter_scope}), and the locals the body writes typed again from them
|
|
345
|
+
# ({.with_scanned_locals}), since the enclosing scan typed those writes before the parameters were bound. A
|
|
346
|
+
# block without parameters keeps `scope`.
|
|
347
|
+
def block_scope(call_node, block, scope)
|
|
348
|
+
bound = block_parameter_scope(call_node, block, scope)
|
|
349
|
+
bound.equal?(scope) ? scope : with_scanned_locals(block, bound)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# `scope` with every local the block's parameter list names bound, so none reads a binding of the same name
|
|
353
|
+
# from outside the block. A plain required parameter reads what the method yields at its position in
|
|
354
|
+
# `requireds` (`MethodDispatcher.expected_block_param_types`); every other name, a destructured, optional,
|
|
355
|
+
# splat, post, keyword, block or `;`-block-local one, reads `Dynamic[top]`, so a call on it counts as an
|
|
356
|
+
# unresolved callee. `scope` itself when the body reads no parameter where the scan types it
|
|
357
|
+
# ({.reads_parameters?}): the bindings would change nothing.
|
|
358
|
+
def block_parameter_scope(call_node, block, scope)
|
|
359
|
+
parameters = block.parameters
|
|
360
|
+
return scope unless parameters.is_a?(Prism::BlockParametersNode)
|
|
361
|
+
|
|
362
|
+
names = []
|
|
363
|
+
collect_parameter_names(parameters, names)
|
|
364
|
+
return scope if names.empty? || !reads_parameters?(block.body, names)
|
|
365
|
+
|
|
366
|
+
bindings = names.to_h { |name| [name, Type::Combinator.untyped] }
|
|
367
|
+
requireds = parameters.parameters&.requireds || []
|
|
368
|
+
unless requireds.none?(Prism::RequiredParameterNode)
|
|
369
|
+
yielded = yielded_types(call_node, scope)
|
|
370
|
+
requireds.each_with_index do |parameter, index|
|
|
371
|
+
next unless parameter.is_a?(Prism::RequiredParameterNode)
|
|
372
|
+
|
|
373
|
+
bindings[parameter.name] = yielded[index] || Type::Combinator.untyped
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
bindings.reduce(scope) { |acc, (name, type)| acc.with_local(name, type) }
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# What the method `call_node` calls yields its block, by position, or `[]` when that cannot be read.
|
|
380
|
+
def yielded_types(call_node, scope)
|
|
381
|
+
receiver = call_node.receiver ? scope.type_of(call_node.receiver) : scope.self_type
|
|
382
|
+
arguments = call_node.arguments&.arguments || []
|
|
383
|
+
MethodDispatcher.expected_block_param_types(
|
|
384
|
+
receiver_type: receiver, method_name: call_node.name, environment: scope.environment, scope: scope,
|
|
385
|
+
arg_types: arguments.map { |argument| scope.type_of(argument) }
|
|
386
|
+
)
|
|
387
|
+
rescue StandardError
|
|
388
|
+
[]
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# The names a block's parameter list declares: every kind of parameter, a destructured one's parts included,
|
|
392
|
+
# and a `;`-block-local. A default value is not read.
|
|
393
|
+
def collect_parameter_names(node, names)
|
|
394
|
+
case node
|
|
395
|
+
when Prism::RequiredParameterNode, Prism::OptionalParameterNode, Prism::RestParameterNode,
|
|
396
|
+
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode, Prism::KeywordRestParameterNode,
|
|
397
|
+
Prism::BlockParameterNode, Prism::BlockLocalVariableNode
|
|
398
|
+
names << node.name if node.name
|
|
399
|
+
when Prism::Node
|
|
400
|
+
node.rigor_each_child { |child| collect_parameter_names(child, names) }
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# True when the block body `body` reads one of `names` where the scan types it. A read the scan never types is
|
|
405
|
+
# a bare argument of a statement that calls a method on `self` without a literal block (`puts i`): the scan
|
|
406
|
+
# reads such a call by its receiver and name alone, and types none of its arguments. Any other read counts,
|
|
407
|
+
# and so does every read under a body that is not a plain statement list (a `rescue` in a `do` block).
|
|
408
|
+
def reads_parameters?(body, names)
|
|
409
|
+
return false if body.nil?
|
|
410
|
+
return names_read?(body, names) unless body.is_a?(Prism::StatementsNode)
|
|
411
|
+
|
|
412
|
+
body.body.any? { |statement| statement_reads?(statement, names) }
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def statement_reads?(statement, names)
|
|
416
|
+
return names_read?(statement, names) unless self_call_without_block?(statement)
|
|
417
|
+
|
|
418
|
+
arguments = statement.arguments&.arguments || []
|
|
419
|
+
arguments.any? { |argument| !argument.is_a?(Prism::LocalVariableReadNode) && names_read?(argument, names) } ||
|
|
420
|
+
names_read?(statement.block, names)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def self_call_without_block?(node)
|
|
424
|
+
node.is_a?(Prism::CallNode) && (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)) &&
|
|
425
|
+
!node.block.is_a?(Prism::BlockNode)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# True when `node` reads one of `names`: a read, or an operator write, which reads the local before it writes.
|
|
429
|
+
def names_read?(node, names)
|
|
430
|
+
case node
|
|
431
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode,
|
|
432
|
+
Prism::LocalVariableAndWriteNode
|
|
433
|
+
return true if names.include?(node.name)
|
|
434
|
+
end
|
|
435
|
+
return false unless node.is_a?(Prism::Node)
|
|
436
|
+
|
|
437
|
+
found = false
|
|
438
|
+
node.rigor_each_child { |child| found ||= names_read?(child, names) }
|
|
439
|
+
found
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
private_class_method :collect_local_writes, :yielded_types, :collect_parameter_names, :reads_parameters?,
|
|
443
|
+
:statement_reads?, :self_call_without_block?, :names_read?
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
# The lookup half of {MutationWidening}'s Hash seam.
|
|
8
|
+
#
|
|
9
|
+
# `Hash#default=`, `Hash#default_proc=` and `Hash#compare_by_identity` mutate the receiver without storing,
|
|
10
|
+
# removing or rewriting a pair. What they change is what a READ of the pairs answers, and a closed `HashShape`
|
|
11
|
+
# makes a claim about exactly that: a key outside `pairs` is provably missing, so `[]` reads `Constant[nil]`.
|
|
12
|
+
# `counts = { a: 1 }; counts.default = 0` kept that claim, and `counts[:b] + 1` drew an error-level
|
|
13
|
+
# `call.undefined-method` on code Ruby runs and prints 1 for.
|
|
14
|
+
#
|
|
15
|
+
# They are kept out of {MutationWidening::HASH_MUTATORS} on purpose. That table answers "the pair set changed",
|
|
16
|
+
# and its other readers act on that answer: the widening there replaces the shape with a `Hash[K, V]` nominal,
|
|
17
|
+
# which costs every present key's value for a mutation that left every pair where it was, and the `non-empty-hash`
|
|
18
|
+
# witness reads it as a store or a removal. This module answers the narrower question instead, so the present keys
|
|
19
|
+
# keep their values. The effect classifier asks only whether the receiver was written, which each of these three
|
|
20
|
+
# does, so it reads the two tables as one ({Effects::MutationClassifier::HASH_MUTATORS}).
|
|
21
|
+
module HashLookupMutation
|
|
22
|
+
MUTATORS = %i[default= default_proc= compare_by_identity].to_set.freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# The binding `shape` holds after `method_name`, or `nil` when the call changes no read of it.
|
|
27
|
+
#
|
|
28
|
+
# `default=` / `default_proc=` open the shape: a key outside `pairs` now reads the default, which the shape
|
|
29
|
+
# cannot state, so it reads `untyped` — the answer an open shape gives an undeclared key. A present key still
|
|
30
|
+
# reads its own value. A default proc can also store (`proc { |h, k| h[k] = [] }`), so a read may add a pair,
|
|
31
|
+
# which is what the open policy says too: `size` / `keys` / `key?` stop folding.
|
|
32
|
+
#
|
|
33
|
+
# `compare_by_identity` changes the key a read has to pass, not the answer to a miss, so it leaves a closed
|
|
34
|
+
# shape closed. What it can falsify is a HIT: see {.identity_sensitive?}.
|
|
35
|
+
def widen_shape(shape, method_name)
|
|
36
|
+
case method_name
|
|
37
|
+
when :default=, :default_proc= then open_shape(shape)
|
|
38
|
+
when :compare_by_identity then identity_widening(shape) if identity_sensitive?(shape)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def open_shape(shape)
|
|
43
|
+
return nil if shape.open?
|
|
44
|
+
|
|
45
|
+
Type::HashShape.new(
|
|
46
|
+
shape.pairs,
|
|
47
|
+
required_keys: shape.required_keys,
|
|
48
|
+
optional_keys: shape.optional_keys,
|
|
49
|
+
read_only_keys: shape.read_only_keys,
|
|
50
|
+
extra_keys: :open
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# True when some key of `shape` stops finding its pair under `compare_by_identity`. A read spells its key as a
|
|
55
|
+
# new literal, and after the switch it hits only when that literal is the very object the hash stored. For a
|
|
56
|
+
# Symbol, `true` / `false` / `nil` and an Integer in the fixnum range it always is. A String key is the frozen
|
|
57
|
+
# copy the literal stored, which a later `"k"` is not unless `# frozen_string_literal: true` interns both — so
|
|
58
|
+
# `s = { "k" => 1 }; s.compare_by_identity; s["k"]` is `nil` in one file and `1` in the next — and a bignum or
|
|
59
|
+
# a heap Float is a fresh object per evaluation.
|
|
60
|
+
#
|
|
61
|
+
# A shape whose every key is identity-stable reads exactly as before; any other is widened by
|
|
62
|
+
# {.identity_widening}.
|
|
63
|
+
def identity_sensitive?(shape)
|
|
64
|
+
shape.pairs.each_key.any? { |key| !identity_stable_key?(key) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# An identity-sensitive shape widened as a storing mutator widens it, its values unpinned, with a `Dynamic[top]`
|
|
68
|
+
# arm on the value side. A read of a String key may find its pair or miss it and answer `nil`, or a default set
|
|
69
|
+
# before or after, and the nominal cannot say which: the arm keeps that read from folding (`s["k"] == 1`) or
|
|
70
|
+
# drawing `possible nil receiver` in the file where the key does hit. It also keeps the answer independent of
|
|
71
|
+
# order — a nominal ignores a later `default=`, so without the arm `s.compare_by_identity; s.default = "x"` read
|
|
72
|
+
# `s["zz"]` as `Integer`.
|
|
73
|
+
def identity_widening(shape)
|
|
74
|
+
key, value = MutationWidening.widen_hash_shape(shape, values: :widen).type_args
|
|
75
|
+
Type::Combinator.nominal_of("Hash", type_args: [key, Type::Combinator.union(value, Type::Combinator.untyped)])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# `Integer#bit_length` is at most 62 for exactly CRuby's 64-bit fixnum range, `-(2**62)..(2**62 - 1)`.
|
|
79
|
+
def identity_stable_key?(key)
|
|
80
|
+
case key
|
|
81
|
+
when Symbol, true, false, nil then true
|
|
82
|
+
when Integer then key.bit_length <= 62
|
|
83
|
+
else false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -41,10 +41,9 @@ module Rigor
|
|
|
41
41
|
|
|
42
42
|
# Reduce `app` against the registry.
|
|
43
43
|
#
|
|
44
|
-
# @param
|
|
45
|
-
# @param fuel [Integer] reduction-step budget (default 64 per ADR-20 WD3). Each visited
|
|
44
|
+
# @param fuel — reduction-step budget (default 64 per ADR-20 WD3). Each visited
|
|
46
45
|
# body node costs one unit. On exhaustion the reduction returns `app.bound`.
|
|
47
|
-
# @return
|
|
46
|
+
# @return the reduced type, or `app.bound` when reduction is impossible (URI
|
|
48
47
|
# not defined, arity mismatch, body_tree absent, fuel exhausted).
|
|
49
48
|
def reduce(app, fuel: DEFAULT_FUEL)
|
|
50
49
|
raise ArgumentError, "expected a Rigor::Type::App, got #{app.class}" unless app.is_a?(Type::App)
|
|
@@ -99,8 +99,6 @@ module Rigor
|
|
|
99
99
|
|
|
100
100
|
attr_reader :registrations, :definitions
|
|
101
101
|
|
|
102
|
-
# @param registrations [Array<Registration>]
|
|
103
|
-
# @param definitions [Array<Definition>]
|
|
104
102
|
def initialize(registrations: [], definitions: [])
|
|
105
103
|
@registrations = registrations.to_h { |r| [r.uri, r] }.freeze
|
|
106
104
|
@definitions = definitions.to_h { |d| [d.uri, d] }.freeze
|
|
@@ -123,7 +121,7 @@ module Rigor
|
|
|
123
121
|
@definitions[uri]
|
|
124
122
|
end
|
|
125
123
|
|
|
126
|
-
# @return
|
|
124
|
+
# @return a new registry whose entries are the union of this registry's and
|
|
127
125
|
# `other`'s. On URI collisions `other`'s entries win (last-write-wins; OQ3 tentative).
|
|
128
126
|
def merge(other)
|
|
129
127
|
raise ArgumentError, "merge target must be an HktRegistry, got #{other.class}" unless other.is_a?(HktRegistry)
|
|
@@ -154,13 +152,12 @@ module Rigor
|
|
|
154
152
|
# diagnostic; the other annotations still apply). That claim was false until issue #785: the
|
|
155
153
|
# production reporter has no `#record`, so every declined directive was dropped silently.
|
|
156
154
|
#
|
|
157
|
-
# @param
|
|
158
|
-
# @param base [HktRegistry] starting registry (typically the bundled
|
|
155
|
+
# @param base — starting registry (typically the bundled
|
|
159
156
|
# `Rigor::Builtins::HktBuiltins.registry`).
|
|
160
|
-
# @param name_scope
|
|
157
|
+
# @param name_scope — threaded through to the bound
|
|
161
158
|
# resolver for class-name lookups; safe to omit during scanning since hkt bounds are
|
|
162
159
|
# typically `untyped` or stdlib classes.
|
|
163
|
-
# @param reporter
|
|
160
|
+
# @param reporter — same fail-soft reporter contract
|
|
164
161
|
# the other RBS-extended parsers use; a collecting double that responds only to `#record` /
|
|
165
162
|
# `#<<` still works (see {Rigor::RbsExtended::HktDirectives.record_hkt_error}).
|
|
166
163
|
|
|
@@ -17,17 +17,29 @@ module Rigor
|
|
|
17
17
|
# `||=` / `&&=` are conditional at runtime, but a widening may only LOSE precision, so answering on the branch that
|
|
18
18
|
# does not store is safe.
|
|
19
19
|
#
|
|
20
|
-
# `Prism::IndexTargetNode` is
|
|
21
|
-
# it
|
|
20
|
+
# `Prism::IndexTargetNode` stores through `[]=` too, but it is absent from {NODE_CLASSES}: it is a TARGET, and the
|
|
21
|
+
# value it stores comes from the construct that owns it — a multi-assign slot (`h[:a], z = 1, 2`), a `for` index
|
|
22
|
+
# (`for h[:a] in xs`, alone, splatted or in a multi-target) or a rescue reference (`rescue => h[:e]`). Only that
|
|
23
|
+
# construct can type the value, so `StatementEvaluator#eval_multi_write`, `#bind_for_index` and
|
|
24
|
+
# `#bind_rescue_reference` observe the straight-line write, passing each target to {.widen} with the slot
|
|
25
|
+
# {MultiTargetBinder} decomposed for it, the element or the rescued exception. {CONTENT_WRITE_NODE_CLASSES} adds
|
|
26
|
+
# it for the nested-block write-back, and `ScopeIndexer`'s pre-pass, which needs no stored value, names the class
|
|
27
|
+
# next to {NODE_CLASSES}.
|
|
22
28
|
module IndexWriteWidening
|
|
23
29
|
NODE_CLASSES = [Prism::IndexOrWriteNode, Prism::IndexAndWriteNode, Prism::IndexOperatorWriteNode].freeze
|
|
24
30
|
|
|
31
|
+
# Every node that stores through `[]=` without being a `[]=` call: {NODE_CLASSES} plus the index TARGET a
|
|
32
|
+
# multi-assign, `rescue =>` or `for` writes through. `StatementEvaluator`'s captured-local write-back widens
|
|
33
|
+
# a receiver a nested block stores into through any of them, `ExpressionTyper`'s block-return threading
|
|
34
|
+
# gate predicts that widening, and `CapturedLocals.content_mutations` finds the per-element fold's in-place
|
|
35
|
+
# captures by it, so all three read this one list.
|
|
36
|
+
CONTENT_WRITE_NODE_CLASSES = [*NODE_CLASSES, Prism::IndexTargetNode].freeze
|
|
37
|
+
|
|
25
38
|
# The method these forms store through — the name the mutator tables are keyed on.
|
|
26
39
|
MUTATOR = :[]=
|
|
27
40
|
|
|
28
41
|
module_function
|
|
29
42
|
|
|
30
|
-
# @param node [Prism::Node]
|
|
31
43
|
def index_write?(node)
|
|
32
44
|
NODE_CLASSES.any? { |klass| node.is_a?(klass) }
|
|
33
45
|
end
|
|
@@ -36,10 +48,7 @@ module Rigor
|
|
|
36
48
|
# the widening joins the stored value into the carrier's content evidence exactly as a real `[]=` does
|
|
37
49
|
# (issue #560). Empty means "no evidence" and widens without joining.
|
|
38
50
|
#
|
|
39
|
-
# @param node
|
|
40
|
-
# @param current_scope [Rigor::Scope]
|
|
41
|
-
# @param arg_types [Array<Rigor::Type::Base>]
|
|
42
|
-
# @return [Rigor::Scope]
|
|
51
|
+
# @param node — one of {NODE_CLASSES}, or a `Prism::IndexTargetNode`
|
|
43
52
|
def widen(node:, current_scope:, arg_types: MutationWidening::NO_ARG_TYPES)
|
|
44
53
|
MutationWidening.widen_receiver_aliases(node.receiver, MUTATOR, current_scope, arg_types: arg_types)
|
|
45
54
|
end
|