rigortype 0.4.6 → 0.4.8
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 +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +29 -10
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +92 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +81 -5
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +123 -25
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +287 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +48 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +81 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +32 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +238 -37
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +82 -3
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../typing_state"
|
|
6
|
+
require_relative "../source/node_children"
|
|
7
|
+
require_relative "closure_escape_analyzer"
|
|
8
|
+
require_relative "construction_escape"
|
|
9
|
+
require_relative "../memo_audit"
|
|
10
|
+
|
|
11
|
+
module Rigor
|
|
12
|
+
module Inference
|
|
13
|
+
# Issue #2608, review rounds 1 and 2 of #2621 — which locals of a body another holder may write, so a remover on
|
|
14
|
+
# them is not narrowed ({RemoverNarrowing}): a narrowing is a fact about the object, and a write through any other
|
|
15
|
+
# holder after the call would outlive it. One flow-insensitive scan per frame body; a local is a holder's when:
|
|
16
|
+
#
|
|
17
|
+
# - a closure that may run later references it: a lambda, or a block passed to a call that is not a catalogued
|
|
18
|
+
# iterator or `tap` / `then` / `yield_self` (`proc { l << 1 }`), created before or after the remover;
|
|
19
|
+
# - it is read anywhere inside the value of any write (local, instance, class, global or constant variable, an
|
|
20
|
+
# index or attribute write, an operator or `||=` / `&&=` write, a multiple assignment) or inside the subject of a
|
|
21
|
+
# `case`/`in`, `=>` or `in` pattern: `al = c ? l : []`, `al = l.push(1)`, `@k = l`, `case l; in al`. The local
|
|
22
|
+
# such a write or pattern binds is a holder too (both names of a copy), unless the write's value is a fresh
|
|
23
|
+
# object: an Array or Hash literal, or `new` / `[]` on a bare or top-level `Array`, `Hash` or `Set`
|
|
24
|
+
# (`a = Array.new(n, "a")`);
|
|
25
|
+
# - a method's parameter, when its body has a zero-argument `super`, which passes every parameter on;
|
|
26
|
+
# - it is read inside an Array element, a Hash key or value, a splat, or an argument of any call, `yield` or
|
|
27
|
+
# `super` (positional, keyword, splat or block-pass), unless the read is the receiver of a call that answers
|
|
28
|
+
# a value other than the receiver and keeps no reference to it ({PURE_READERS}: #2555's per-class
|
|
29
|
+
# `ConstructionEscape::READERS`, less the conversions that return self on another class): `puts(l.size)`,
|
|
30
|
+
# `dump_type(l.dup)` and `dump_type(l.first)` hold nothing, `Keep.new(l)` and `foo(l.push(1))` do;
|
|
31
|
+
# - it receives a reflective call (`l.send(...)`, `l.instance_eval { }`, `l.tap { }`);
|
|
32
|
+
# - and every local, when the body calls `binding`, `eval`, `local_variable_get` or `local_variable_set` on any
|
|
33
|
+
# receiver (`-> {}.binding`), or a receiverless `instance_variable_set`.
|
|
34
|
+
#
|
|
35
|
+
# A receiver position, a `return` and a body's last expression hand nothing on.
|
|
36
|
+
module RemoverHolderScan
|
|
37
|
+
REFLECTIVE_CALLS = %i[
|
|
38
|
+
send public_send __send__ instance_eval instance_exec tap then yield_self itself method
|
|
39
|
+
define_singleton_method
|
|
40
|
+
].to_set.freeze
|
|
41
|
+
# A conversion one class lists as a reader returns the receiver itself on another (`Array#to_a`, `Hash#to_h`,
|
|
42
|
+
# `Set#to_set`), so the union of the per-class lists leaves them out.
|
|
43
|
+
SELF_CONVERSIONS = %i[to_a to_h to_set].freeze
|
|
44
|
+
PURE_READERS = (ConstructionEscape::READERS.values.reduce(:|) - SELF_CONVERSIONS).freeze
|
|
45
|
+
IMMEDIATE_BLOCK_CALLS = %i[tap then yield_self].to_set.freeze
|
|
46
|
+
FRAME_CALLS = %i[binding eval local_variable_get local_variable_set instance_variable_set].to_set.freeze
|
|
47
|
+
# Frame calls that reach the locals whatever their receiver (`pr.binding`, `Kernel.eval`).
|
|
48
|
+
ANY_RECEIVER_FRAME_CALLS = %i[binding eval local_variable_get local_variable_set].to_set.freeze
|
|
49
|
+
NON_WRITER_OPERATORS = %i[== != <= >= ===].to_set.freeze
|
|
50
|
+
CORE_COLLECTIONS = %i[Array Hash Set].to_set.freeze
|
|
51
|
+
LOCAL_REFERENCES = [
|
|
52
|
+
Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode, Prism::LocalVariableOperatorWriteNode,
|
|
53
|
+
Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode, Prism::LocalVariableTargetNode
|
|
54
|
+
].freeze
|
|
55
|
+
LOCAL_WRITES = [
|
|
56
|
+
Prism::LocalVariableWriteNode, Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode,
|
|
57
|
+
Prism::LocalVariableAndWriteNode
|
|
58
|
+
].freeze
|
|
59
|
+
ALL = :all
|
|
60
|
+
NO_ARGUMENTS = [].freeze
|
|
61
|
+
# ADR-129 WD1 — a run-scoped Memo, emptied when a run ends so no verdict outlives the body it read: keyed by
|
|
62
|
+
# body node, and the unsafe names are a pure function of the body and its frame's parameters.
|
|
63
|
+
UNSAFE_CACHE = TypingState.memo(
|
|
64
|
+
:remover_narrowing_unsafe,
|
|
65
|
+
run_scoped: true, audit: "remover narrowing unsafe",
|
|
66
|
+
reason: "keyed by the body node; the unsafe local names are a pure function of the body"
|
|
67
|
+
)
|
|
68
|
+
private_constant :REFLECTIVE_CALLS, :SELF_CONVERSIONS, :PURE_READERS, :IMMEDIATE_BLOCK_CALLS, :FRAME_CALLS,
|
|
69
|
+
:ANY_RECEIVER_FRAME_CALLS, :NON_WRITER_OPERATORS, :CORE_COLLECTIONS,
|
|
70
|
+
:LOCAL_REFERENCES, :LOCAL_WRITES, :ALL, :NO_ARGUMENTS, :UNSAFE_CACHE
|
|
71
|
+
|
|
72
|
+
module_function
|
|
73
|
+
|
|
74
|
+
# Whether the local `name` may be written through another holder somewhere in the body `scope` runs. A scope
|
|
75
|
+
# with no known body answers true.
|
|
76
|
+
def unsafe_receiver?(scope, name)
|
|
77
|
+
frame = scope.match_frame
|
|
78
|
+
body = frame&.body
|
|
79
|
+
return true if body.nil?
|
|
80
|
+
|
|
81
|
+
names = memoised_unsafe_names(body, frame.parameters)
|
|
82
|
+
names.equal?(ALL) || names.include?(name.to_sym)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# {.unsafe_names} of `body`, memoised for the run.
|
|
86
|
+
def memoised_unsafe_names(body, parameters)
|
|
87
|
+
return unsafe_names(body, parameters) if MemoAudit.enabled? && MemoAudit.suspended?
|
|
88
|
+
|
|
89
|
+
table = TypingState.current.weak_key_map(UNSAFE_CACHE)
|
|
90
|
+
stored = table[body]
|
|
91
|
+
if stored.nil?
|
|
92
|
+
return table[body] = MemoAudit.key_audit("remover narrowing unsafe") { unsafe_names(body, parameters) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
MemoAudit.check("remover narrowing unsafe", stored, body) { unsafe_names(body, parameters) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# `names` collects the locals; a collector that answers `site=` ({AliasMutationFloor}) is told, before each name
|
|
99
|
+
# it is given, the node that hands the local on, or nil when no single node does (a capture by a closure that may
|
|
100
|
+
# run later, a zero-argument `super`), reads value positions itself when it answers `value_held` ({.precise?}),
|
|
101
|
+
# and is told when the walk enters (`enter(node, parent)`) and leaves
|
|
102
|
+
# (`leave(node)`) each node. One that answers `keeps_arguments?` says which calls' plain arguments it counts as
|
|
103
|
+
# held.
|
|
104
|
+
def unsafe_names(body, parameters = nil, names = Set.new)
|
|
105
|
+
sited = names.respond_to?(:site=)
|
|
106
|
+
catch(ALL) do
|
|
107
|
+
collect_unsafe(body, false, names, sited:)
|
|
108
|
+
names.site = nil if sited
|
|
109
|
+
parameter_names(parameters, names) if forwards_parameters?(body)
|
|
110
|
+
return names.freeze
|
|
111
|
+
end
|
|
112
|
+
ALL
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def collect_unsafe(node, in_closure, names, sited: false, parent: nil)
|
|
116
|
+
in_closure ||= node.is_a?(Prism::LambdaNode)
|
|
117
|
+
if sited
|
|
118
|
+
names.enter(node, parent)
|
|
119
|
+
names.site = in_closure ? nil : node
|
|
120
|
+
end
|
|
121
|
+
names << node.name if in_closure && LOCAL_REFERENCES.any? { |k| node.is_a?(k) }
|
|
122
|
+
record_node(node, names)
|
|
123
|
+
|
|
124
|
+
node.rigor_each_child do |child|
|
|
125
|
+
child_in_closure = in_closure || (child.is_a?(Prism::BlockNode) && deferred_block?(node))
|
|
126
|
+
collect_unsafe(child, child_in_closure, names, sited:, parent: node)
|
|
127
|
+
end
|
|
128
|
+
names.leave(node) if sited
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def record_node(node, names) # rubocop:disable Metrics/CyclomaticComplexity
|
|
132
|
+
case node
|
|
133
|
+
when Prism::CallNode then record_call(node, names)
|
|
134
|
+
when Prism::YieldNode, Prism::SuperNode then node.arguments&.arguments&.each { |a| record_held(a, names) }
|
|
135
|
+
when Prism::ArrayNode then node.elements.each { |element| record_held(element, names) }
|
|
136
|
+
when Prism::AssocNode
|
|
137
|
+
record_held(node.key, names)
|
|
138
|
+
record_held(node.value, names)
|
|
139
|
+
when Prism::SplatNode, Prism::BlockArgumentNode then record_held(node.expression, names)
|
|
140
|
+
when Prism::CaseMatchNode then record_pattern(node.predicate, node.conditions, names)
|
|
141
|
+
when Prism::MatchRequiredNode, Prism::MatchPredicateNode then record_pattern(node.value, [node.pattern], names)
|
|
142
|
+
when Prism::MultiWriteNode then record_multi_write(node, names)
|
|
143
|
+
else record_write(node, names)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def record_call(node, names)
|
|
148
|
+
throw ALL if frame_call?(node)
|
|
149
|
+
|
|
150
|
+
receiver = unparenthesised(node.receiver)
|
|
151
|
+
names << receiver.name if receiver.is_a?(Prism::LocalVariableReadNode) && REFLECTIVE_CALLS.include?(node.name)
|
|
152
|
+
arguments = node.arguments&.arguments || NO_ARGUMENTS
|
|
153
|
+
if writer_call?(node) && !arguments.empty?
|
|
154
|
+
record_writer_call(arguments, names)
|
|
155
|
+
elsif keeps_arguments?(node, names)
|
|
156
|
+
arguments.each { |argument| record_held(argument, names) }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def record_writer_call(arguments, names)
|
|
161
|
+
*keys, value = arguments
|
|
162
|
+
keys.each { |key| record_held(key, names) }
|
|
163
|
+
precise?(names) ? names.value_held(value) : every_read(value, names)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def keeps_arguments?(node, names) = !names.respond_to?(:keeps_arguments?) || names.keeps_arguments?(node)
|
|
167
|
+
|
|
168
|
+
# A collector that reads a value position itself ({AliasMutationFloor}): `value_held(node)` for a value handed
|
|
169
|
+
# to another holder (an element, an argument, an index or attribute write's value), `value_written(node)` for a
|
|
170
|
+
# write node, and `value_bound(value, targets)` for a multiple assignment or a pattern. The default collector
|
|
171
|
+
# reads every local inside the value instead.
|
|
172
|
+
def precise?(names) = names.respond_to?(:value_held)
|
|
173
|
+
|
|
174
|
+
# A zero-argument `super` passes every parameter of the method on, implicitly. (A `...` forward passes only the
|
|
175
|
+
# anonymous parameters, which name no local.)
|
|
176
|
+
def forwards_parameters?(node)
|
|
177
|
+
return true if node.is_a?(Prism::ForwardingSuperNode)
|
|
178
|
+
|
|
179
|
+
node.rigor_each_child { |child| return true if forwards_parameters?(child) }
|
|
180
|
+
false
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def parameter_names(node, names)
|
|
184
|
+
return if node.nil?
|
|
185
|
+
|
|
186
|
+
names << node.name if node.class.name.end_with?("ParameterNode") && node.respond_to?(:name) && node.name
|
|
187
|
+
node.rigor_each_child { |child| parameter_names(child, names) }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def frame_call?(node)
|
|
191
|
+
ANY_RECEIVER_FRAME_CALLS.include?(node.name) || (node.receiver.nil? && FRAME_CALLS.include?(node.name))
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def unparenthesised(node)
|
|
195
|
+
node = node.body.body.first while node.is_a?(Prism::ParenthesesNode) && node.body&.body&.size == 1
|
|
196
|
+
node
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# An index or attribute write (`h[k] = v`, `o.x = v`, `o&.x = v`): its value is a write's value.
|
|
200
|
+
def writer_call?(node)
|
|
201
|
+
name = node.name
|
|
202
|
+
name.end_with?("=") && !NON_WRITER_OPERATORS.include?(name)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# A write's value hands on every local read anywhere inside it, and the local it binds holds what it read.
|
|
206
|
+
def record_write(node, names)
|
|
207
|
+
return unless node.class.name.end_with?("WriteNode") && node.respond_to?(:value)
|
|
208
|
+
return names.value_written(node) if precise?(names)
|
|
209
|
+
return unless every_read(node.value, names)
|
|
210
|
+
|
|
211
|
+
names << node.name if LOCAL_WRITES.any? { |k| node.is_a?(k) } && !fresh_value?(node.value)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# `Array`, `Hash` or `Set`, bare or as a top-level path (`::Set`): a project class may define its own `self.[]` or
|
|
215
|
+
# `self.new` that returns its argument.
|
|
216
|
+
def core_collection_constant?(node)
|
|
217
|
+
top_level = node.is_a?(Prism::ConstantReadNode) || (node.is_a?(Prism::ConstantPathNode) && node.parent.nil?)
|
|
218
|
+
top_level && CORE_COLLECTIONS.include?(node.name)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# A value that is a new object whatever it reads, so the local it binds aliases nothing.
|
|
222
|
+
def fresh_value?(value)
|
|
223
|
+
case value
|
|
224
|
+
when Prism::ArrayNode, Prism::HashNode then true
|
|
225
|
+
when Prism::CallNode then %i[new []].include?(value.name) && core_collection_constant?(value.receiver)
|
|
226
|
+
else false
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def record_multi_write(node, names)
|
|
231
|
+
return names.value_bound(node.value, [*node.lefts, node.rest, *node.rights]) if precise?(names)
|
|
232
|
+
return unless every_read(node.value, names)
|
|
233
|
+
|
|
234
|
+
local_targets([*node.lefts, node.rest, *node.rights], names)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def record_pattern(subject, patterns, names)
|
|
238
|
+
return names.value_bound(subject, patterns) if precise?(names)
|
|
239
|
+
return unless every_read(subject, names)
|
|
240
|
+
|
|
241
|
+
local_targets(patterns, names)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Adds every local `node` reads, at any depth; answers whether it read any.
|
|
245
|
+
def every_read(node, names)
|
|
246
|
+
return false if node.nil?
|
|
247
|
+
|
|
248
|
+
found = node.is_a?(Prism::LocalVariableReadNode)
|
|
249
|
+
names << node.name if found
|
|
250
|
+
node.rigor_each_child { |child| found = true if every_read(child, names) }
|
|
251
|
+
found
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def local_targets(nodes, names)
|
|
255
|
+
nodes.compact.each do |node|
|
|
256
|
+
names << node.name if node.is_a?(Prism::LocalVariableTargetNode)
|
|
257
|
+
local_targets(node.compact_child_nodes, names)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# A held position: every local read inside it but the receiver of a {PURE_READERS} call.
|
|
262
|
+
def record_held(node, names)
|
|
263
|
+
return if node.nil?
|
|
264
|
+
return names.value_held(node) if precise?(names)
|
|
265
|
+
return if pure_read?(node)
|
|
266
|
+
|
|
267
|
+
names << node.name if node.is_a?(Prism::LocalVariableReadNode)
|
|
268
|
+
node.rigor_each_child { |child| record_held(child, names) }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# A {PURE_READERS} call on a local hands the local on to nothing. Its own arguments are held positions of the
|
|
272
|
+
# call itself, which the body walk records when it reaches that call.
|
|
273
|
+
def pure_read?(node)
|
|
274
|
+
node.is_a?(Prism::CallNode) && PURE_READERS.include?(node.name) &&
|
|
275
|
+
node.receiver.is_a?(Prism::LocalVariableReadNode)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# A block that may run after its call returns: any but a catalogued iterator's, or `tap` / `then` /
|
|
279
|
+
# `yield_self`'s.
|
|
280
|
+
def deferred_block?(call_node)
|
|
281
|
+
return true unless call_node.is_a?(Prism::CallNode)
|
|
282
|
+
|
|
283
|
+
!(ClosureEscapeAnalyzer.iterator_name?(call_node.name) || IMMEDIATE_BLOCK_CALLS.include?(call_node.name))
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../type"
|
|
6
|
+
require_relative "remover_holder_scan"
|
|
7
|
+
# `Narrowing` is resolved at call time: requiring it here closes a load cycle through `KeyPresenceGuard`.
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
module Inference
|
|
11
|
+
# Issue #2608 — a collection remover or filter narrows its receiver's element (or Hash key / value) type by what
|
|
12
|
+
# Ruby guarantees it removed, where that is provable from the call alone:
|
|
13
|
+
#
|
|
14
|
+
# - `Array#compact!` / `Hash#compact!` drop `nil` from the element / value;
|
|
15
|
+
# - `Array#delete(nil)` / `Set#delete(nil)` / `Set#delete?(nil)` drop `nil` from the element, and `Hash#delete(nil)`
|
|
16
|
+
# drops it from the key, given a literal `nil`;
|
|
17
|
+
# - `reject!` / `delete_if` keep the elements the block answers falsey for, and `select!` / `filter!` / `keep_if`
|
|
18
|
+
# the ones it answers truthy for. The block's one expression is read by the engine's own predicate narrowing
|
|
19
|
+
# ({Narrowing.predicate_scopes}) with its parameter bound to the element (the Hash `|k, v|` form binds both), and
|
|
20
|
+
# the surviving element is that parameter's binding on the matching edge. `&:nil?` reads as `{ |x| x.nil? }`;
|
|
21
|
+
# - a closed `HashShape` loses the key a literal `delete(:k)` names.
|
|
22
|
+
#
|
|
23
|
+
# Everything else (`pop`, `shift`, `slice!`, `subtract`, `delete` of a non-literal, a block whose predicate the
|
|
24
|
+
# narrowing does not read) answers `nil`: which members remain is not provable, and the caller keeps its reading.
|
|
25
|
+
#
|
|
26
|
+
# Only a receiver that is itself a local read narrows, and only when no other holder in its body may write the
|
|
27
|
+
# object ({RemoverHolderScan}): the narrowing is a fact about the object. A receiver selecting among bindings
|
|
28
|
+
# (`(f ? a : b).compact!`) mutates one of them, and narrowing every candidate would drop a member from the one the
|
|
29
|
+
# call never touched. An instance variable never narrows: any method, or any object that holds it, can write it.
|
|
30
|
+
module RemoverNarrowing
|
|
31
|
+
NIL_REJECTORS = %i[compact!].to_set.freeze
|
|
32
|
+
NIL_LITERAL_DELETERS = %i[delete delete?].to_set.freeze
|
|
33
|
+
FALSEY_KEEPERS = %i[reject! delete_if].to_set.freeze
|
|
34
|
+
TRUTHY_KEEPERS = %i[select! filter! keep_if].to_set.freeze
|
|
35
|
+
CANDIDATES = (NIL_REJECTORS | NIL_LITERAL_DELETERS | FALSEY_KEEPERS | TRUTHY_KEEPERS).freeze
|
|
36
|
+
NARROWING_RECEIVERS = [Prism::LocalVariableReadNode].freeze
|
|
37
|
+
CARRIERS = %w[Array Hash Set].freeze
|
|
38
|
+
# Nodes that make a one-expression block's value something other than its predicate, or rebind a parameter.
|
|
39
|
+
OPAQUE_BLOCK_NODES = [
|
|
40
|
+
Prism::NextNode, Prism::BreakNode, Prism::ReturnNode, Prism::RedoNode, Prism::RetryNode, Prism::YieldNode,
|
|
41
|
+
Prism::LocalVariableWriteNode, Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode,
|
|
42
|
+
Prism::LocalVariableAndWriteNode, Prism::LocalVariableTargetNode
|
|
43
|
+
].freeze
|
|
44
|
+
private_constant :NIL_REJECTORS, :NIL_LITERAL_DELETERS, :FALSEY_KEEPERS, :TRUTHY_KEEPERS, :NARROWING_RECEIVERS,
|
|
45
|
+
:CARRIERS, :OPAQUE_BLOCK_NODES
|
|
46
|
+
|
|
47
|
+
module_function
|
|
48
|
+
|
|
49
|
+
# Whether `call_node` may narrow its receiver at all: a cheap syntactic gate for {.narrow}.
|
|
50
|
+
def candidate?(call_node)
|
|
51
|
+
CANDIDATES.include?(call_node.name) && NARROWING_RECEIVERS.any? { |klass| call_node.receiver.is_a?(klass) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The closed `HashShape` `shape` without the key a literal `delete(:k)` names, or `nil` when the call is no such
|
|
55
|
+
# delete or the shape is open (an unseen key may be the one deleted, and the opened default reads it back).
|
|
56
|
+
def narrow_shape(shape, call_node)
|
|
57
|
+
return nil unless shape.is_a?(Type::HashShape) && shape.closed? && call_node.name == :delete
|
|
58
|
+
|
|
59
|
+
key = literal_key(single_argument(call_node))
|
|
60
|
+
return nil if key.equal?(NO_KEY)
|
|
61
|
+
return shape unless shape.pairs.key?(key)
|
|
62
|
+
return nil if shape.read_only_key?(key)
|
|
63
|
+
|
|
64
|
+
Type::Combinator.hash_shape_of(
|
|
65
|
+
shape.pairs.except(key),
|
|
66
|
+
required_keys: shape.required_keys - [key], optional_keys: shape.optional_keys - [key],
|
|
67
|
+
read_only_keys: shape.read_only_keys, extra_keys: :closed
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# `type` with each Array / Hash / Set member narrowed by what `call_node` removes, member-wise through a union.
|
|
72
|
+
# `nil` when the call narrows no member (it is no exact remover for the member's class, or its block predicate
|
|
73
|
+
# says nothing about the element). A member the call answers for without change (`compact!` on `Array[Integer]`)
|
|
74
|
+
# still counts as answered: the caller then keeps the precise reading rather than a floored one.
|
|
75
|
+
def narrow(type, call_node, scope)
|
|
76
|
+
answered = false
|
|
77
|
+
members = members_of(type).map do |member|
|
|
78
|
+
narrowed = narrow_member(member, call_node, scope)
|
|
79
|
+
next member if narrowed.nil?
|
|
80
|
+
|
|
81
|
+
answered = true
|
|
82
|
+
narrowed
|
|
83
|
+
end
|
|
84
|
+
answered ? Type::Combinator.union(*members) : nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def members_of(type)
|
|
88
|
+
type.is_a?(Type::Union) ? type.members : [type]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def narrow_member(member, call_node, scope)
|
|
92
|
+
return nil unless member.is_a?(Type::Nominal) && CARRIERS.include?(member.class_name)
|
|
93
|
+
|
|
94
|
+
args = member.type_args
|
|
95
|
+
case member.class_name
|
|
96
|
+
when "Hash" then args.size == 2 ? narrow_hash(member, call_node, scope) : nil
|
|
97
|
+
else args.size == 1 ? narrow_elements(member, call_node, scope) : nil
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def narrow_elements(member, call_node, scope)
|
|
102
|
+
element = member.type_args.first
|
|
103
|
+
narrowed =
|
|
104
|
+
case call_node.name
|
|
105
|
+
when :compact! then Narrowing.narrow_non_nil(element)
|
|
106
|
+
when :delete, :delete? then nil_argument?(call_node) ? Narrowing.narrow_non_nil(element) : nil
|
|
107
|
+
else block_narrowed(call_node, scope, [element])&.first
|
|
108
|
+
end
|
|
109
|
+
usable?(narrowed) ? Type::Combinator.nominal_of(member.class_name, type_args: [narrowed]) : nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def narrow_hash(member, call_node, scope)
|
|
113
|
+
key, value = member.type_args
|
|
114
|
+
narrowed =
|
|
115
|
+
case call_node.name
|
|
116
|
+
when :compact! then [key, Narrowing.narrow_non_nil(value)]
|
|
117
|
+
when :delete then nil_argument?(call_node) ? [Narrowing.narrow_non_nil(key), value] : nil
|
|
118
|
+
when :delete? then nil
|
|
119
|
+
else block_narrowed(call_node, scope, [key, value])
|
|
120
|
+
end
|
|
121
|
+
narrowed&.all? { |arg| usable?(arg) } ? Type::Combinator.nominal_of("Hash", type_args: narrowed) : nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# A parameter narrowed to `bot` (`select! { |x| x.nil? }` on `Array[Integer]`) says the carrier is empty; that
|
|
125
|
+
# is left to the caller's reading rather than spelled as `Array[bot]`.
|
|
126
|
+
def usable?(type)
|
|
127
|
+
!type.nil? && !type.is_a?(Type::Bot)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def nil_argument?(call_node)
|
|
131
|
+
single_argument(call_node).is_a?(Prism::NilNode)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def single_argument(call_node)
|
|
135
|
+
arguments = call_node.arguments&.arguments
|
|
136
|
+
arguments&.size == 1 ? arguments.first : nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
NO_KEY = Object.new.freeze
|
|
140
|
+
private_constant :NO_KEY
|
|
141
|
+
|
|
142
|
+
def literal_key(node)
|
|
143
|
+
case node
|
|
144
|
+
when Prism::SymbolNode then node.unescaped.to_sym
|
|
145
|
+
else NO_KEY
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The block parameters' types on the edge that keeps an element, or `nil` when the call has no block the
|
|
150
|
+
# narrowing reads: one expression, no jump or local write, explicit required parameters matching the carrier
|
|
151
|
+
# (`|x|` for an element, `|k, v|` for a Hash pair), and a predicate that narrows one of them on some edge.
|
|
152
|
+
def block_narrowed(call_node, scope, types)
|
|
153
|
+
keep = keep_edge(call_node.name)
|
|
154
|
+
return nil if keep.nil?
|
|
155
|
+
|
|
156
|
+
block = call_node.block
|
|
157
|
+
return symbol_nil_narrowed(block, keep, types) if block.is_a?(Prism::BlockArgumentNode)
|
|
158
|
+
|
|
159
|
+
names = block_parameter_names(block, types.size)
|
|
160
|
+
predicate = names && block_predicate(block)
|
|
161
|
+
return nil if predicate.nil?
|
|
162
|
+
|
|
163
|
+
predicate_narrowed(predicate, scope, names, types, keep)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def keep_edge(method_name)
|
|
167
|
+
if FALSEY_KEEPERS.include?(method_name) then :falsey
|
|
168
|
+
elsif TRUTHY_KEEPERS.include?(method_name) then :truthy
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def symbol_nil_narrowed(block, keep, types)
|
|
173
|
+
symbol = block.expression
|
|
174
|
+
return nil unless types.size == 1 && symbol.is_a?(Prism::SymbolNode) && symbol.unescaped == "nil?"
|
|
175
|
+
|
|
176
|
+
[keep == :falsey ? Narrowing.narrow_non_nil(types.first) : Narrowing.narrow_nil(types.first)]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def block_parameter_names(block, arity)
|
|
180
|
+
return nil unless block.is_a?(Prism::BlockNode)
|
|
181
|
+
|
|
182
|
+
parameters = block.parameters
|
|
183
|
+
return nil unless parameters.is_a?(Prism::BlockParametersNode)
|
|
184
|
+
|
|
185
|
+
list = parameters.parameters
|
|
186
|
+
return nil if list.nil? || !simple_parameters?(list) || list.requireds.size != arity
|
|
187
|
+
return nil unless list.requireds.all?(Prism::RequiredParameterNode)
|
|
188
|
+
|
|
189
|
+
list.requireds.map(&:name)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def simple_parameters?(list)
|
|
193
|
+
list.optionals.empty? && list.rest.nil? && list.posts.empty? && list.keywords.empty? &&
|
|
194
|
+
list.keyword_rest.nil? && list.block.nil?
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def block_predicate(block)
|
|
198
|
+
body = block.body
|
|
199
|
+
return nil unless body.is_a?(Prism::StatementsNode) && body.body.size == 1
|
|
200
|
+
|
|
201
|
+
statement = body.body.first
|
|
202
|
+
opaque?(statement) ? nil : statement
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def opaque?(node)
|
|
206
|
+
return true if OPAQUE_BLOCK_NODES.any? { |klass| node.is_a?(klass) }
|
|
207
|
+
|
|
208
|
+
node.compact_child_nodes.any? { |child| opaque?(child) }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def predicate_narrowed(predicate, scope, names, types, keep)
|
|
212
|
+
bound = names.zip(types).reduce(scope) { |acc, (name, type)| acc.with_local(name, type) }
|
|
213
|
+
truthy, falsey = Narrowing.predicate_scopes(predicate, bound)
|
|
214
|
+
read = ->(edge) { names.map { |name| edge.local(name) || bound.local(name) } }
|
|
215
|
+
entry_types = read.call(bound)
|
|
216
|
+
truthy_types = read.call(truthy)
|
|
217
|
+
falsey_types = read.call(falsey)
|
|
218
|
+
return nil if truthy_types == entry_types && falsey_types == entry_types
|
|
219
|
+
|
|
220
|
+
keep == :truthy ? truthy_types : falsey_types
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -24,7 +24,8 @@ module Rigor
|
|
|
24
24
|
# replacement, and the seed it would otherwise keep beside it is gone.
|
|
25
25
|
REPLACED = Ractor.make_shareable({
|
|
26
26
|
"Array" => { map!: [0], collect!: [0], flatten!: [0], replace: [0] },
|
|
27
|
-
"Hash" => { transform_keys!: [0], transform_values!: [1], replace: [0, 1] }
|
|
27
|
+
"Hash" => { transform_keys!: [0], transform_values!: [1], replace: [0, 1] },
|
|
28
|
+
"Set" => { map!: [0], collect!: [0], flatten!: [0], replace: [0] }
|
|
28
29
|
})
|
|
29
30
|
|
|
30
31
|
# Mutators that store values nothing describes BESIDE values they may keep: `fill(x, start)` leaves the slots
|
|
@@ -66,6 +67,56 @@ module Rigor
|
|
|
66
67
|
Type::Combinator.nominal_of(carrier.class_name, type_args: args)
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
# The element rewriters {.typed} answers for, keyed to the non-bang call whose result is the rewritten
|
|
71
|
+
# receiver: `a.map!(&:to_i)` holds what `a.map(&:to_i)` returns, and `a.flatten!` what `a.flatten` does.
|
|
72
|
+
TYPED_COUNTERPARTS = Ractor.make_shareable({ map!: :map, collect!: :collect, flatten!: :flatten })
|
|
73
|
+
|
|
74
|
+
# The carrier classes {.typed} rewrites, with the rewriters each takes. `Set#flatten!` is absent: it flattens
|
|
75
|
+
# only nested Sets, so `ARGV.map { |x| [x] }.to_set.flatten!` still holds `[String]` Arrays, while `Set#flatten`
|
|
76
|
+
# answers an element its RBS does not narrow.
|
|
77
|
+
TYPED_CARRIERS = Ractor.make_shareable({ "Array" => %i[map! collect! flatten!], "Set" => %i[map! collect!] })
|
|
78
|
+
|
|
79
|
+
# Issue #2427 — `carrier` with its element replaced by `element`, the element of what the non-bang counterpart
|
|
80
|
+
# ({TYPED_COUNTERPARTS}) answers for the same call: `a = ARGV.dup; a.map!(&:to_i)` reads `Array[Integer]` and
|
|
81
|
+
# `a.map!(&:strip)` keeps `Array[String]`. It is the typed form of {.arm}'s {REPLACED} answer, and it replaces
|
|
82
|
+
# the gradual and the precise element alike, since nothing the seed held survives the rewrite.
|
|
83
|
+
#
|
|
84
|
+
# Answers for an `Array` / `Set` nominal and an empty-witness refinement of one (keeping the witness only where
|
|
85
|
+
# {RefinementMutation.preserves_witness?} does: `map!` keeps it, `flatten!` can empty `[[]]`). `nil` for any
|
|
86
|
+
# other carrier or name, so the caller takes the gradual arm instead.
|
|
87
|
+
def typed(carrier, method_name, element)
|
|
88
|
+
return nil unless TYPED_COUNTERPARTS.key?(method_name)
|
|
89
|
+
|
|
90
|
+
case carrier
|
|
91
|
+
when Type::Nominal
|
|
92
|
+
return nil unless carrier.type_args.size == 1 && TYPED_CARRIERS[carrier.class_name]&.include?(method_name)
|
|
93
|
+
|
|
94
|
+
Type::Combinator.nominal_of(carrier.class_name, type_args: [element])
|
|
95
|
+
when Type::Difference
|
|
96
|
+
typed_refinement(carrier, method_name, element)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def typed_refinement(difference, method_name, element)
|
|
101
|
+
base = difference.base
|
|
102
|
+
return nil unless difference.removes_empty_witness? && base.is_a?(Type::Nominal)
|
|
103
|
+
|
|
104
|
+
rewritten = typed(base, method_name, element)
|
|
105
|
+
return nil if rewritten.nil?
|
|
106
|
+
|
|
107
|
+
kept = RefinementMutation.preserves_witness?(base.class_name, method_name)
|
|
108
|
+
kept ? Type::Combinator.difference(rewritten, difference.removed) : rewritten
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The element of `result`, the non-bang counterpart's answer: an `Array[R]` / `Set[R]` nominal, or an
|
|
112
|
+
# empty-witness refinement of one. `nil` when the answer has no single element to read.
|
|
113
|
+
def counterpart_element(result)
|
|
114
|
+
result = result.base if result.is_a?(Type::Difference) && result.removes_empty_witness?
|
|
115
|
+
return nil unless result.is_a?(Type::Nominal) && %w[Array Set].include?(result.class_name)
|
|
116
|
+
|
|
117
|
+
result.type_args.size == 1 ? result.type_args.first : nil
|
|
118
|
+
end
|
|
119
|
+
|
|
69
120
|
# {.arm} through a `Union` (member by member) and an empty-witness refinement (its base, keeping the witness
|
|
70
121
|
# only when the mutator cannot empty the receiver — {RefinementMutation.preserves_witness?}). For a carrier
|
|
71
122
|
# the ADR-56 slice-C join rebuilt from its seed, which knows the adders' content but not the rewrite.
|
|
@@ -114,7 +165,7 @@ module Rigor
|
|
|
114
165
|
when Type::Nominal then type.class_name
|
|
115
166
|
end
|
|
116
167
|
end
|
|
117
|
-
private_class_method :value_carrier_class, :arm_refinement
|
|
168
|
+
private_class_method :value_carrier_class, :arm_refinement, :typed_refinement
|
|
118
169
|
end
|
|
119
170
|
end
|
|
120
171
|
end
|
|
@@ -6,8 +6,9 @@ module Rigor
|
|
|
6
6
|
module Inference
|
|
7
7
|
module ScopeIndexer
|
|
8
8
|
# The `class_cvars` discovery table (slice 7 phase 6) as a {DeclarationWalk} collector: every
|
|
9
|
-
# `@@x = …` a `def` body writes, typed under the census scope and unioned per class
|
|
10
|
-
#
|
|
9
|
+
# `@@x = …` a `def` body writes, typed under the census scope and unioned per class, joined by what a
|
|
10
|
+
# `class` / `module` body writes to the same cvar. Ruby cvars are shared by the instance and singleton
|
|
11
|
+
# facets, so every `def` counts, `def self.x` included.
|
|
11
12
|
class ClassCvarsCollector
|
|
12
13
|
include DeclarationWalk::Collector
|
|
13
14
|
|
|
@@ -15,6 +16,14 @@ module Rigor
|
|
|
15
16
|
@table = {}
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
# Issue #3204 — a `class` / `module` body's own writes, outside its `def`s, under the body's context;
|
|
20
|
+
# each only joins an entry a `def` body's write creates. An unnameable cref (a header below
|
|
21
|
+
# `class << expr`) leaves the prefix empty, and the gather skips it.
|
|
22
|
+
def on_declaration(node, _context, body)
|
|
23
|
+
ScopeIndexer.collect_body_cvar_writes(node.body, body.prefix, body.scope, @table)
|
|
24
|
+
DeclarationWalk::DESCEND
|
|
25
|
+
end
|
|
26
|
+
|
|
18
27
|
# `@@x` in a `def` resolves through the LEXICAL cref: `Module.nesting` is the same inside a meta-new or
|
|
19
28
|
# eval block, so the context's `prefix` keys the write and its rebound `self` never does —
|
|
20
29
|
# `K = Class.new { def m = @@x }` inside `class C` writes `C::@@x`. The walk goes no further in: the
|
|
@@ -25,10 +34,8 @@ module Rigor
|
|
|
25
34
|
DeclarationWalk::DECLINE
|
|
26
35
|
end
|
|
27
36
|
|
|
28
|
-
# The table as the discovery index holds it
|
|
29
|
-
def table
|
|
30
|
-
@table.transform_values(&:freeze).freeze
|
|
31
|
-
end
|
|
37
|
+
# The table as the discovery index holds it ({ScopeIndexer#finished_cvar_census}).
|
|
38
|
+
def table = ScopeIndexer.finished_cvar_census(@table)
|
|
32
39
|
end
|
|
33
40
|
end
|
|
34
41
|
end
|