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
|
@@ -13,6 +13,7 @@ require_relative "guard_rebinding"
|
|
|
13
13
|
require_relative "intervening_ivar_reset"
|
|
14
14
|
require_relative "key_presence_guard"
|
|
15
15
|
require_relative "last_line"
|
|
16
|
+
require_relative "match_rebinding"
|
|
16
17
|
require_relative "operand_effects"
|
|
17
18
|
require_relative "optimistic_origin"
|
|
18
19
|
require_relative "project_method_ownership"
|
|
@@ -532,7 +533,17 @@ module Rigor
|
|
|
532
533
|
lexical_class_name(condition, scope) || case_equality_target_class(condition)
|
|
533
534
|
end
|
|
534
535
|
body_scope = record_bot_guard(narrow_guarded_slot(body_scope, slot, truthy_type), slot, truthy_type, classes)
|
|
535
|
-
|
|
536
|
+
marked = unwrap_case_subject(subject).is_a?(Prism::LocalVariableReadNode) && scope.gradual_narrowing?(slot.name)
|
|
537
|
+
# A marked subject keeps its mark under any `when` (`when "r"` narrows it without writing it); an unmarked one
|
|
538
|
+
# is stamped only under class conditions.
|
|
539
|
+
if marked || classes.none?(&:nil?)
|
|
540
|
+
static = gradual_union_slot?(slot) ? gradual_static_part(slot.current) : nil
|
|
541
|
+
static &&= case_when_types(scope, static, conditions).first
|
|
542
|
+
body_scope = mark_gradual_narrowing(body_scope, slot, truthy_type, marked: marked, narrowed_static: static)
|
|
543
|
+
end
|
|
544
|
+
falsey_scope = narrow_guarded_slot(scope, slot, falsey_type)
|
|
545
|
+
falsey_scope = falsey_scope.with_gradual_narrowing_mark(slot.name) if marked
|
|
546
|
+
[body_scope, falsey_scope]
|
|
536
547
|
end
|
|
537
548
|
|
|
538
549
|
CASE_SUBJECT_KINDS = %i[local ivar global constant].freeze
|
|
@@ -588,12 +599,21 @@ module Rigor
|
|
|
588
599
|
end
|
|
589
600
|
end
|
|
590
601
|
|
|
591
|
-
# When
|
|
592
|
-
# match-data globals
|
|
593
|
-
#
|
|
594
|
-
#
|
|
595
|
-
#
|
|
596
|
-
#
|
|
602
|
+
# When every condition is a `RegularExpressionNode` literal, the body edge narrows the
|
|
603
|
+
# match-data globals as `analyse_regex_match_predicate`'s truthy edge does. With multiple
|
|
604
|
+
# regex conditions (`when /a/, /b/`) the body is reachable through any of them, so only
|
|
605
|
+
# `$~`/`$&` are safely non-nil; numbered groups whose presence differs per alternative stay
|
|
606
|
+
# `String | nil`. With no regex condition the entry scope passes through unchanged.
|
|
607
|
+
#
|
|
608
|
+
# Issue #1474 — a clause that mixes a regex literal with another condition (`when /(a)/,
|
|
609
|
+
# "lit"`) is also entered through the other conditions: each regex literal before them ran
|
|
610
|
+
# its `===` and failed, leaving the globals nil, and a condition before any regex leaves
|
|
611
|
+
# them as they were. The body scope then joins the match globals of every way in rather
|
|
612
|
+
# than claiming the regex matched: each condition contributes the scope its own `===`
|
|
613
|
+
# leaves — a regex literal's truthy edge, the falsey edge of the regex literals that
|
|
614
|
+
# failed before it, the entry state where none ran, and the unknown-match edges for a
|
|
615
|
+
# condition that may itself be or run a Regexp ({MatchRebinding::Operands.pattern_value?},
|
|
616
|
+
# {MatchRebinding.may_match?}).
|
|
597
617
|
def apply_when_regex_globals(conditions, scope)
|
|
598
618
|
regexes = conditions.grep(Prism::RegularExpressionNode)
|
|
599
619
|
return scope if regexes.empty?
|
|
@@ -604,8 +624,102 @@ module Rigor
|
|
|
604
624
|
else
|
|
605
625
|
Set.new
|
|
606
626
|
end
|
|
607
|
-
truthy, = regex_match_predicate_scopes(scope, unconditional)
|
|
608
|
-
truthy
|
|
627
|
+
truthy, falsey = regex_match_predicate_scopes(scope, unconditional)
|
|
628
|
+
return truthy if regexes.size == conditions.size
|
|
629
|
+
|
|
630
|
+
empty_truthy, empty_falsey = regex_match_predicate_scopes(scope, Set.new)
|
|
631
|
+
failed = scope
|
|
632
|
+
paths = conditions.map do |condition|
|
|
633
|
+
kind = when_regex_condition_kind(condition, scope)
|
|
634
|
+
path, failed = when_regex_condition_scopes(kind, failed, truthy, falsey, empty_truthy, empty_falsey)
|
|
635
|
+
path
|
|
636
|
+
end
|
|
637
|
+
paths.reduce(:join)
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# How a `when` condition's `===` can touch the match globals, for {#apply_when_regex_globals}:
|
|
641
|
+
# `:literal` a `RegularExpressionNode`, whose match the clause's shared unconditional groups
|
|
642
|
+
# describe; `:interpolated` an interpolated regex literal, a definite match whose groups are
|
|
643
|
+
# unknown; `:may_match` any other condition that may be a Regexp or run one, whose `===`
|
|
644
|
+
# outcome for the globals is unknown; `:neutral` a condition whose `===` and evaluation run
|
|
645
|
+
# no match, leaving the globals as the earlier conditions left them.
|
|
646
|
+
def when_regex_condition_kind(condition, scope)
|
|
647
|
+
return :literal if condition.is_a?(Prism::RegularExpressionNode)
|
|
648
|
+
return :interpolated if condition.is_a?(Prism::InterpolatedRegularExpressionNode)
|
|
649
|
+
if MatchRebinding::Operands.pattern_value?(condition, scope) ||
|
|
650
|
+
MatchRebinding.may_match?(condition, scope)
|
|
651
|
+
return :may_match
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
:neutral
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
# `[body_path_scope, failed_scope]` for one `when` condition of a mixed clause, `failed`
|
|
658
|
+
# being the globals state the conditions before it left. On the path where this condition's
|
|
659
|
+
# `===` matched the body reads the truthy edge (`:literal`), the ungrouped truthy edge
|
|
660
|
+
# (`:interpolated`), the join of that edge with `failed` (`:may_match` — it may not have
|
|
661
|
+
# been a match at all), or `failed` itself (`:neutral`). On the path where it failed the
|
|
662
|
+
# next conditions read the matching falsey edge, the ungrouped one, the join of it with
|
|
663
|
+
# `failed`, or `failed`.
|
|
664
|
+
def when_regex_condition_scopes(kind, failed, truthy, falsey, empty_truthy, empty_falsey)
|
|
665
|
+
case kind
|
|
666
|
+
when :literal then [truthy, falsey]
|
|
667
|
+
when :interpolated then [empty_truthy, empty_falsey]
|
|
668
|
+
when :may_match then [empty_truthy.join(failed), failed.join(empty_falsey)]
|
|
669
|
+
else [failed, failed]
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Issue #2443, #1371 — the scope the block of a call that yields only after it has matched in the caller's frame
|
|
674
|
+
# enters with ({MatchRebinding::YieldingMatch}): the match edge of `pattern`, the call's pattern argument, over
|
|
675
|
+
# `scope` with the match globals forgotten. `$~` is a `MatchData`, `$&` and the pre- and post-match a `String`,
|
|
676
|
+
# and so is each numbered group {#yielded_capture_groups} vouches for; every other group is unbound, so it
|
|
677
|
+
# reads the RBS `String?` whatever an earlier match in the method bound it to.
|
|
678
|
+
def yielded_match_scope(scope, pattern)
|
|
679
|
+
regex_match_predicate_scopes(scope, yielded_capture_groups(pattern)).first
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
# The pattern sources {#yielded_capture_groups} reads groups from: literal characters, `.`, the anchors, the
|
|
683
|
+
# quantifiers, `|`, plain and `(?:…)` groups, the class escapes, `\n`, `\r` and `\t`, an escaped punctuation
|
|
684
|
+
# character, and a bracket class holding only those with no bracket inside it. The group walk
|
|
685
|
+
# ({#unconditional_capture_groups}) reads these right; it misreads sources outside them (#2472, #1476).
|
|
686
|
+
PLAIN_PATTERN_ATOM = %r{[A-Za-z0-9 _\-.,:;=<>!@%&~'"`/^$+*?|(){}#]|\\[wWdDsShHnrt]|\\[^A-Za-z0-9\s]}
|
|
687
|
+
PLAIN_PATTERN_CLASS =
|
|
688
|
+
%r{\[\^?(?:[A-Za-z0-9 _\-.,:;=<>!@%~'"`/^$+*?|(){}#]|\\[wWdDsShHnrt]|\\[^A-Za-z0-9\s])+\]}
|
|
689
|
+
PLAIN_PATTERN = /\A(?:#{PLAIN_PATTERN_CLASS}|#{PLAIN_PATTERN_ATOM}|\\[bBAzZG])*\z/
|
|
690
|
+
OTHER_GROUP_FORM = /\(\?(?!:)/
|
|
691
|
+
# A quantifier on a quantifier repeats the quantified atom, so `(a)+{0}` and `(a){2}?` may match without the
|
|
692
|
+
# group. `+?` and `++` are one quantifier.
|
|
693
|
+
STACKED_QUANTIFIER = /\+[?+]?[*{]|\+[?+][?+]|\}[*+?{]/
|
|
694
|
+
NO_GROUPS = Set.new.freeze
|
|
695
|
+
private_constant :PLAIN_PATTERN_ATOM, :PLAIN_PATTERN_CLASS, :PLAIN_PATTERN, :OTHER_GROUP_FORM,
|
|
696
|
+
:STACKED_QUANTIFIER, :NO_GROUPS
|
|
697
|
+
|
|
698
|
+
# Issue #1371 — the 1-based indices of the groups every match of `pattern` sets, read only from a regex
|
|
699
|
+
# literal written between slashes, without interpolation or the `x` flag, whose source is a {PLAIN_PATTERN}
|
|
700
|
+
# with no `(?` form but `(?:` and no {STACKED_QUANTIFIER}. Any other pattern, a String one included, vouches
|
|
701
|
+
# for no group: declining costs precision, while a misread group would read `String` where Ruby has nil. A
|
|
702
|
+
# `%r` literal is declined because its source escapes the delimiter, which hides a quantifier from the walk:
|
|
703
|
+
# `%r,(a){\,1}b,` is `/(a){,1}b/`.
|
|
704
|
+
def yielded_capture_groups(pattern)
|
|
705
|
+
return NO_GROUPS unless pattern.is_a?(Prism::RegularExpressionNode) && !pattern.extended?
|
|
706
|
+
return NO_GROUPS unless pattern.opening == "/"
|
|
707
|
+
|
|
708
|
+
plain_pattern_source?(pattern.content) ? unconditional_capture_groups(pattern.content) : NO_GROUPS
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
def plain_pattern_source?(source)
|
|
712
|
+
source.ascii_only? && PLAIN_PATTERN.match?(source) && !OTHER_GROUP_FORM.match?(source) &&
|
|
713
|
+
!STACKED_QUANTIFIER.match?(source)
|
|
714
|
+
end
|
|
715
|
+
private_class_method :yielded_capture_groups, :plain_pattern_source?
|
|
716
|
+
|
|
717
|
+
# Issue #2443 — true when every member of `type` is a String (a project subclass included) or a `Dynamic` that
|
|
718
|
+
# is untyped or may hold only Strings, Symbols and nil ({#match_member_kind}). A `nil` member never runs the
|
|
719
|
+
# block, so it counts neither way.
|
|
720
|
+
def string_match_receiver?(type, scope)
|
|
721
|
+
kinds = match_member_kinds(type, scope) - [:nil]
|
|
722
|
+
!kinds.empty? && kinds.all? { |kind| %i[string string_subclass dynamic].include?(kind) }
|
|
609
723
|
end
|
|
610
724
|
|
|
611
725
|
# Internal analyser. Returns `[truthy_scope, falsey_scope]` when the predicate shape is
|
|
@@ -616,7 +730,30 @@ module Rigor
|
|
|
616
730
|
def analyse(node, scope)
|
|
617
731
|
edges = analyse_shape(node, scope)
|
|
618
732
|
reader = last_line_condition(node)
|
|
619
|
-
|
|
733
|
+
edges = LastLine.predicate_scopes(reader, scope, edges) if reader
|
|
734
|
+
keep_gradual_marks(node, scope, edges)
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
# Issue #2656 — a guard edge narrows a local; it does not write it. So a local a class guard marked as a gradual
|
|
738
|
+
# narrowing ({Scope#with_gradual_narrowing_mark}) keeps the mark on both edges of every later predicate
|
|
739
|
+
# (`return if x.nil?`, `x != ""`, a second class guard), which bind the narrowed type through `with_local` and
|
|
740
|
+
# drop it. A local the predicate itself writes (`if (x = v)`) loses it, as any write does.
|
|
741
|
+
def keep_gradual_marks(node, scope, edges)
|
|
742
|
+
return edges if edges.nil? || (edges[0].equal?(scope) && edges[1].equal?(scope))
|
|
743
|
+
|
|
744
|
+
names = scope.gradual_narrowing_names
|
|
745
|
+
return edges if names.empty?
|
|
746
|
+
|
|
747
|
+
# Defensive: the statement walk binds a predicate's own write (`if (x = v)`) before its edges are read, so the
|
|
748
|
+
# mark is already gone there; the exclusion keeps a write that reached here from carrying it.
|
|
749
|
+
names -= OperandEffects.written_variables(node).map(&:to_sym)
|
|
750
|
+
return edges if names.empty?
|
|
751
|
+
|
|
752
|
+
edges.map do |edge|
|
|
753
|
+
names.reduce(edge) do |acc, name|
|
|
754
|
+
acc.gradual_narrowing?(name) || acc.local(name).nil? ? acc : acc.with_gradual_narrowing_mark(name)
|
|
755
|
+
end
|
|
756
|
+
end
|
|
620
757
|
end
|
|
621
758
|
|
|
622
759
|
# The reader call a condition's value is, or nil.
|
|
@@ -968,12 +1105,22 @@ module Rigor
|
|
|
968
1105
|
end
|
|
969
1106
|
|
|
970
1107
|
def analyse_local_read(node, scope)
|
|
971
|
-
|
|
1108
|
+
local_truthiness_edges(node.name, scope)
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
# The falsey edge is where an optimistic lookup's miss lands (ADR-101, issue #2306): `Scope#with_local`
|
|
1112
|
+
# drops the issue #286 mark as a write does, so the edge re-takes the one `scope` holds. Without it the
|
|
1113
|
+
# join after `if v; …; end` with no `else` lost the mark on both arms and read `v` as nil-free, although
|
|
1114
|
+
# a miss reaches that join through the implicit else. The truthy edge proves the value non-nil, so it
|
|
1115
|
+
# keeps dropping the mark.
|
|
1116
|
+
def local_truthiness_edges(name, scope)
|
|
1117
|
+
current = scope.local(name)
|
|
972
1118
|
return nil if current.nil?
|
|
973
1119
|
|
|
1120
|
+
falsey = scope.with_local(name, narrow_falsey(current))
|
|
974
1121
|
[
|
|
975
|
-
scope.with_local(
|
|
976
|
-
|
|
1122
|
+
scope.with_local(name, narrow_truthy(current)),
|
|
1123
|
+
OptimisticOrigin.carry_local_mark(scope, falsey, name, nil)
|
|
977
1124
|
]
|
|
978
1125
|
end
|
|
979
1126
|
|
|
@@ -987,13 +1134,7 @@ module Rigor
|
|
|
987
1134
|
# holds the just-bound value; the narrowing contract on the surrounding `if` is the
|
|
988
1135
|
# same.
|
|
989
1136
|
def analyse_local_write(node, scope)
|
|
990
|
-
|
|
991
|
-
return nil if current.nil?
|
|
992
|
-
|
|
993
|
-
[
|
|
994
|
-
scope.with_local(node.name, narrow_truthy(current)),
|
|
995
|
-
scope.with_local(node.name, narrow_falsey(current))
|
|
996
|
-
]
|
|
1137
|
+
local_truthiness_edges(node.name, scope)
|
|
997
1138
|
end
|
|
998
1139
|
|
|
999
1140
|
# Truthy guard on an instance variable. Covers both the bare read — `if @ivar`, the
|
|
@@ -1007,9 +1148,11 @@ module Rigor
|
|
|
1007
1148
|
current = scope.ivar(node.name)
|
|
1008
1149
|
return nil if current.nil?
|
|
1009
1150
|
|
|
1151
|
+
# The falsey edge keeps the optimistic mark, as {#local_truthiness_edges} explains.
|
|
1152
|
+
falsey = scope.with_ivar(node.name, narrow_falsey(current))
|
|
1010
1153
|
[
|
|
1011
1154
|
scope.with_ivar(node.name, narrow_truthy(current)),
|
|
1012
|
-
|
|
1155
|
+
OptimisticOrigin.carry_ivar_mark(scope, falsey, node.name, nil)
|
|
1013
1156
|
]
|
|
1014
1157
|
end
|
|
1015
1158
|
|
|
@@ -1180,15 +1323,6 @@ module Rigor
|
|
|
1180
1323
|
refinements
|
|
1181
1324
|
end
|
|
1182
1325
|
|
|
1183
|
-
# Recognised CallNode predicates:
|
|
1184
|
-
# - `recv.nil?` (Slice 6 phase 1, no args, no block)
|
|
1185
|
-
# - unary `!recv` (`name == :!`, no args, no block)
|
|
1186
|
-
# - `recv.is_a?(C)` / `recv.kind_of?(C)` / `recv.instance_of?(C)` with a single
|
|
1187
|
-
# static-constant argument and no block (Slice 6 phase 2 sub-phase 1).
|
|
1188
|
-
# - `local == literal` / `literal == local` and the `!=` mirror for trusted static
|
|
1189
|
-
# literals (Slice 6 phase 2 sub-phase 2).
|
|
1190
|
-
# Anything else returns nil so the surrounding analyser falls through to the
|
|
1191
|
-
# no-narrowing fallback.
|
|
1192
1326
|
# Issue #1223 — `(x = e).nil?`, `(x ||= e).empty?`, `(@x = e).is_a?(C)`. A write's value is the binding
|
|
1193
1327
|
# it leaves, and a predicate is narrowed in the scope after its receiver ran, so the call narrows the
|
|
1194
1328
|
# variable exactly as the same call on a read of it would. Every analyser keys its receiver on a read
|
|
@@ -1798,7 +1932,7 @@ module Rigor
|
|
|
1798
1932
|
|
|
1799
1933
|
def match_dynamic_kind(member, scope)
|
|
1800
1934
|
facet = member.static_facet
|
|
1801
|
-
return :dynamic if
|
|
1935
|
+
return :dynamic if Type::Combinator.untyped?(member)
|
|
1802
1936
|
|
|
1803
1937
|
facet_kinds = match_member_kinds(facet, scope)
|
|
1804
1938
|
:dynamic if facet_kinds.all? { |kind| (STRING_MATCH_KINDS + [:nil]).include?(kind) }
|
|
@@ -1960,7 +2094,7 @@ module Rigor
|
|
|
1960
2094
|
|
|
1961
2095
|
# Returns the Set of 1-based numbered-capture indices that are UNCONDITIONAL in
|
|
1962
2096
|
# `source`: present on every successful match because no optional quantifier (`?`,
|
|
1963
|
-
# `*`, `{0,…}`) applies to the group or any ancestor group, and the pattern contains no
|
|
2097
|
+
# `*`, `{0,…}`, `{,…}`) applies to the group or any ancestor group, and the pattern contains no
|
|
1964
2098
|
# alternation (`|`). Optional and alternation-reachable groups are excluded — at
|
|
1965
2099
|
# runtime `$N` is `nil` for them even when the overall match succeeds, so narrowing
|
|
1966
2100
|
# them to non-nil `String` would be unsound. The walker is intentionally light (char
|
|
@@ -2044,13 +2178,15 @@ module Rigor
|
|
|
2044
2178
|
end
|
|
2045
2179
|
|
|
2046
2180
|
# True when the group whose closing paren is at `source[pos]` is followed by a
|
|
2047
|
-
# quantifier that permits zero repetitions (`?`, `*`, `{0…}`). `+` and `{1,…}`
|
|
2048
|
-
# make a group optional. A lazy/possessive suffix (`*?`, `*+`) is still
|
|
2049
|
-
# on the base quantifier.
|
|
2181
|
+
# quantifier that permits zero repetitions (`?`, `*`, `{0…}`, `{,…}`). `+` and `{1,…}`
|
|
2182
|
+
# do NOT make a group optional. A lazy/possessive suffix (`*?`, `*+`) is still
|
|
2183
|
+
# zero-permitting on the base quantifier. `{,…}` counts only with a digit after the
|
|
2184
|
+
# comma — `{,}`, `{,x}` and `{, }` are literal braces, not quantifiers.
|
|
2050
2185
|
def next_quantifier_optional?(source, pos)
|
|
2051
2186
|
case source[pos]
|
|
2052
2187
|
when "?", "*" then true
|
|
2053
|
-
when "{" then source[pos + 1] == "0"
|
|
2188
|
+
when "{" then source[pos + 1] == "0" ||
|
|
2189
|
+
(source[pos + 1] == "," && source[pos + 2].to_s.match?(/\d/))
|
|
2054
2190
|
else false
|
|
2055
2191
|
end
|
|
2056
2192
|
end
|
|
@@ -2466,10 +2602,10 @@ module Rigor
|
|
|
2466
2602
|
narrowed == original ? :relational : :local_binding
|
|
2467
2603
|
end
|
|
2468
2604
|
|
|
2469
|
-
# `recv.is_a?(C)` / `recv.kind_of?(C)` / `recv.instance_of?(C)` narrowing. The
|
|
2470
|
-
# MUST be a `LocalVariableReadNode` (so we have a name to rebind), and the argument
|
|
2605
|
+
# `recv.is_a?(C)` / `recv.kind_of?(C)` / `recv.instance_of?(C)` narrowing. The argument
|
|
2471
2606
|
# MUST be a single static constant reference (`Foo` or `Foo::Bar`) we can resolve to a
|
|
2472
|
-
# qualified class name.
|
|
2607
|
+
# qualified class name. The receiver is a local, a global, constant or instance variable
|
|
2608
|
+
# ({#receiver_slot}) or a single-hop chain; anything else falls through to "no narrowing".
|
|
2473
2609
|
def analyse_class_predicate(node, scope, exact:)
|
|
2474
2610
|
return nil if node.arguments.nil?
|
|
2475
2611
|
return nil unless node.arguments.arguments.size == 1
|
|
@@ -2519,7 +2655,62 @@ module Rigor
|
|
|
2519
2655
|
truthy = narrow_class(slot.current, class_name, exact: exact, environment: environment, scope: scope)
|
|
2520
2656
|
falsey = narrow_not_class(slot.current, class_name, exact: exact, environment: environment, scope: scope)
|
|
2521
2657
|
truthy_scope = record_bot_guard(narrow_guarded_slot(scope, slot, truthy), slot, truthy, [class_name])
|
|
2522
|
-
|
|
2658
|
+
static = gradual_union_slot?(slot) ? gradual_static_part(slot.current) : nil
|
|
2659
|
+
static &&= narrow_class(static, class_name, exact: exact, environment: environment, scope: scope)
|
|
2660
|
+
[mark_gradual_narrowing(truthy_scope, slot, truthy, narrowed_static: static),
|
|
2661
|
+
narrow_guarded_slot(scope, slot, falsey)]
|
|
2662
|
+
end
|
|
2663
|
+
|
|
2664
|
+
# Issue #2656 — a class guard's truthy edge over a local that held a `Dynamic` value marks the narrowed binding
|
|
2665
|
+
# ({Scope#with_gradual_narrowing_mark}): the binding keeps the asked class's `Nominal`, which dispatch and the
|
|
2666
|
+
# method rules read, while `call.argument-type-mismatch` judges it as the gradual value it came from, which a
|
|
2667
|
+
# literal-typed parameter (`("r" | "w")`) accepts. Only a local is marked, and only when the edge left it
|
|
2668
|
+
# something other than `Dynamic` or `bot`.
|
|
2669
|
+
def mark_gradual_narrowing(scope, slot, narrowed, marked: false, narrowed_static: nil)
|
|
2670
|
+
return scope unless slot.kind == :local
|
|
2671
|
+
|
|
2672
|
+
mark_gradual_local(scope, slot.name, slot.current, narrowed, marked: marked, narrowed_static: narrowed_static)
|
|
2673
|
+
end
|
|
2674
|
+
|
|
2675
|
+
# {#mark_gradual_narrowing} for the local `name`, which held `current` before the guard and `narrowed` after.
|
|
2676
|
+
# `current` is gradual when it is `Dynamic` or a union with a `Dynamic` member (`h[:m] || "r"`, `c ? h[:m] :
|
|
2677
|
+
# nil`); `marked` says the local already carried the mark, so a `case` or a capture over it keeps it (a
|
|
2678
|
+
# predicate keeps it through {.keep_gradual_marks}).
|
|
2679
|
+
def mark_gradual_local(scope, name, current, narrowed, marked: false, narrowed_static: nil)
|
|
2680
|
+
return scope unless marked || gradual_bearing?(current)
|
|
2681
|
+
return scope if narrowed.nil? || narrowed.is_a?(Type::Dynamic) || narrowed.is_a?(Type::Bot)
|
|
2682
|
+
return scope unless marked || literal_or_empty?(narrowed_static || gradual_static_part(current))
|
|
2683
|
+
|
|
2684
|
+
scope.with_gradual_narrowing_mark(name)
|
|
2685
|
+
end
|
|
2686
|
+
public :mark_gradual_local
|
|
2687
|
+
|
|
2688
|
+
def gradual_bearing?(type)
|
|
2689
|
+
type.is_a?(Type::Dynamic) || (type.is_a?(Type::Union) && type.members.any?(Type::Dynamic))
|
|
2690
|
+
end
|
|
2691
|
+
|
|
2692
|
+
# Issue #2656 — the non-`Dynamic` members of a union `current` (nil for any other type). A union is stamped
|
|
2693
|
+
# only when that part, narrowed by the guard (`narrowed_static`, or the part itself where the caller cannot
|
|
2694
|
+
# narrow it), is literals or nothing: the rule judges a literal member as written, so the mark then withholds
|
|
2695
|
+
# only for what came from the gradual member. A static member that narrows to a class (`d = "a" + s.to_s;
|
|
2696
|
+
# x = h[:m] || d`) would be read past as if it were gradual, so such a union is not stamped.
|
|
2697
|
+
def gradual_static_part(current)
|
|
2698
|
+
return nil unless current.is_a?(Type::Union)
|
|
2699
|
+
|
|
2700
|
+
Type::Combinator.union(*current.members.grep_v(Type::Dynamic))
|
|
2701
|
+
end
|
|
2702
|
+
|
|
2703
|
+
# Whether `slot` is a local holding a union with a `Dynamic` member, the one case {.gradual_static_part}
|
|
2704
|
+
# answers for; checked first so a class guard on any other slot builds nothing.
|
|
2705
|
+
def gradual_union_slot?(slot)
|
|
2706
|
+
slot.kind == :local && slot.current.is_a?(Type::Union) && slot.current.members.any?(Type::Dynamic)
|
|
2707
|
+
end
|
|
2708
|
+
|
|
2709
|
+
def literal_or_empty?(type)
|
|
2710
|
+
return true if type.nil?
|
|
2711
|
+
|
|
2712
|
+
members = type.is_a?(Type::Union) ? type.members : [type]
|
|
2713
|
+
members.all? { |member| member.is_a?(Type::Bot) || member.is_a?(Type::Constant) }
|
|
2523
2714
|
end
|
|
2524
2715
|
|
|
2525
2716
|
# The class name a `is_a?` / `kind_of?` / `instance_of?` argument denotes: the top-level
|
|
@@ -2538,14 +2729,13 @@ module Rigor
|
|
|
2538
2729
|
class_predicate_scopes(scope, node.receiver.name, current, class_name, exact: exact)
|
|
2539
2730
|
end
|
|
2540
2731
|
|
|
2541
|
-
# Stable single-hop method-chain narrowing
|
|
2542
|
-
# receiver narrowing across stable receivers"). When the predicate's receiver is
|
|
2732
|
+
# Stable single-hop method-chain narrowing. When the predicate's receiver is
|
|
2543
2733
|
# `<local/ivar>.<method>` with no args and no block, record the truthy / falsey
|
|
2544
2734
|
# narrowing in `Scope#method_chain_narrowings` keyed on the chain address. The
|
|
2545
2735
|
# dominated body's identical chain reads then observe the narrowed type through
|
|
2546
2736
|
# `ExpressionTyper#call_type_for`'s lookup.
|
|
2547
2737
|
#
|
|
2548
|
-
# Heuristic-by-design
|
|
2738
|
+
# Heuristic-by-design: a second call to `x.last` could in
|
|
2549
2739
|
# principle return a different value than the first. The chain is dropped on (1)
|
|
2550
2740
|
# receiver variable rebind (handled inside `Scope#with_local` / `#with_ivar`), and (2)
|
|
2551
2741
|
# any intervening call against the same root receiver (handled by
|
|
@@ -2864,9 +3054,7 @@ module Rigor
|
|
|
2864
3054
|
# - For each fact, the corresponding positional argument (matched by parameter name in
|
|
2865
3055
|
# the selected overload) MUST be a `Prism::LocalVariableReadNode` for narrowing to
|
|
2866
3056
|
# apply.
|
|
2867
|
-
# - When the target is `self`, narrowing applies to the receiver
|
|
2868
|
-
# not yet narrow `self` itself, so `self`-targeted facts are accepted by the merger
|
|
2869
|
-
# but currently produce no scope edits.
|
|
3057
|
+
# - When the target is `self`, narrowing applies to the receiver ({#apply_self_fact}).
|
|
2870
3058
|
def analyse_rbs_extended_contribution(node, scope)
|
|
2871
3059
|
method_def = resolve_rbs_extended_method(node, scope)
|
|
2872
3060
|
return nil if method_def.nil?
|
|
@@ -3403,8 +3591,8 @@ module Rigor
|
|
|
3403
3591
|
# extend record that is structurally incomplete: `Widget.extend(m)` at a call site and an `extend`
|
|
3404
3592
|
# written in a file outside the analysed set are both invisible to the walk behind
|
|
3405
3593
|
# {Scope#singleton_extends_of}, and the declaration-side record joined to it below is no more
|
|
3406
|
-
# complete — RBS says nothing about a class it does not declare. A shape that turned this table
|
|
3407
|
-
# `:subclass` verdict would have to be right about ABSENCE too, and it cannot be. Preserving
|
|
3594
|
+
# complete — RBS says nothing about a class it does not declare. A shape that turned this table
|
|
3595
|
+
# into a positive `:subclass` verdict would have to be right about ABSENCE too, and it cannot be. Preserving
|
|
3408
3596
|
# `Singleton[C]` would also hand the arm a receiver whose singleton method table has no `clamp` on
|
|
3409
3597
|
# it, trading the unreachable-clause false positive for an undefined-method one — the same trap
|
|
3410
3598
|
# #657 recorded on the `Constant` carrier.
|
|
@@ -3586,7 +3774,7 @@ module Rigor
|
|
|
3586
3774
|
rbs_type.is_a?(RBS::Types::Literal) && rbs_type.literal == value
|
|
3587
3775
|
end
|
|
3588
3776
|
|
|
3589
|
-
# The
|
|
3777
|
+
# The three receiver shapes with a scope binding the edges can rebind, mirroring
|
|
3590
3778
|
# `apply_self_fact`. A method chain or arbitrary expression has none, so it reads nil and
|
|
3591
3779
|
# the caller declines.
|
|
3592
3780
|
def receiver_binding_type(receiver, scope)
|
|
@@ -3696,7 +3884,7 @@ module Rigor
|
|
|
3696
3884
|
|
|
3697
3885
|
# Walks a call chain down to its `&.` link, collecting the suffix method names applied
|
|
3698
3886
|
# above it (outermost first). Declines unless the chain bottoms out in a `&.` call on a
|
|
3699
|
-
# direct local
|
|
3887
|
+
# direct local, instance-variable, global or constant read — the slots a scope can rewrite — and unless
|
|
3700
3888
|
# every suffix is a bare reader or predicate, since an argument or a block could make the
|
|
3701
3889
|
# answer depend on something other than the receiver.
|
|
3702
3890
|
#
|
|
@@ -31,6 +31,12 @@ module Rigor
|
|
|
31
31
|
# pure-value operand never reached, so a chained `b.push(2).size`, an argument `puts(b.push(2))` or
|
|
32
32
|
# `d.map!.with_index { … }` left `b` / `d` on the literal their assignment wrote.
|
|
33
33
|
#
|
|
34
|
+
# Issue #3163 — a `def` inside a block or lambda of the expression is looked into for one thing: an in-place
|
|
35
|
+
# mutation of an instance variable, class variable or global ({.method_mutation?}). The block may call the
|
|
36
|
+
# method it defines, and the evaluator floors such a variable after the block
|
|
37
|
+
# (`StatementEvaluator#floor_block_defined_method_mutations`), which it reaches only for an operand this module
|
|
38
|
+
# answers for: `puts([1].each { def h; $buf << 1; end; h })`.
|
|
39
|
+
#
|
|
34
40
|
# Short-circuiting and allocation-free outside a mutator-named call: the evaluator asks it of every call's
|
|
35
41
|
# operands, and asks it again of each operand it threads. A found effect stops the recursion without a
|
|
36
42
|
# `return` out of the child block, which would allocate once per frame it unwinds and make a deep literal
|
|
@@ -58,12 +64,15 @@ module Rigor
|
|
|
58
64
|
].freeze
|
|
59
65
|
JUMP_NODES = Set[Prism::NextNode, Prism::BreakNode].freeze
|
|
60
66
|
SCOPE_NODES = Set[Prism::BlockNode, Prism::LambdaNode].freeze
|
|
67
|
+
NON_LOCAL_READ_NODES = Set[
|
|
68
|
+
Prism::InstanceVariableReadNode, Prism::ClassVariableReadNode, Prism::GlobalVariableReadNode
|
|
69
|
+
].freeze
|
|
61
70
|
OPAQUE_NODES = Set[
|
|
62
71
|
Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefinedNode
|
|
63
72
|
].freeze
|
|
64
73
|
private_constant :LOCAL_WRITE_NODES, :OUTLIVING_WRITE_NODES, :INSTANCE_WRITE_NODES, :GLOBAL_WRITE_NODES,
|
|
65
74
|
:COMPOUND_LOCAL_WRITES,
|
|
66
|
-
:COMPOUND_INSTANCE_WRITES, :JUMP_NODES, :SCOPE_NODES, :OPAQUE_NODES
|
|
75
|
+
:COMPOUND_INSTANCE_WRITES, :JUMP_NODES, :SCOPE_NODES, :NON_LOCAL_READ_NODES, :OPAQUE_NODES
|
|
67
76
|
|
|
68
77
|
module_function
|
|
69
78
|
|
|
@@ -79,7 +88,7 @@ module Rigor
|
|
|
79
88
|
return true if OUTLIVING_WRITE_NODES.include?(klass)
|
|
80
89
|
return true if jumps && JUMP_NODES.include?(klass)
|
|
81
90
|
return true if klass == Prism::CallNode && outliving_mutation?(node, nesting)
|
|
82
|
-
return
|
|
91
|
+
return block_defined_method_mutation?(node, nesting) if OPAQUE_NODES.include?(klass)
|
|
83
92
|
|
|
84
93
|
nesting += 1 if SCOPE_NODES.include?(klass)
|
|
85
94
|
jumps &&= !JumpTargets.boundary?(node)
|
|
@@ -89,6 +98,38 @@ module Rigor
|
|
|
89
98
|
end
|
|
90
99
|
private_class_method :effect?
|
|
91
100
|
|
|
101
|
+
# Issue #3163 — the one effect an opaque node has: a `def` under a block or lambda whose body mutates a
|
|
102
|
+
# non-local in place ({.method_mutation?}).
|
|
103
|
+
def block_defined_method_mutation?(node, nesting)
|
|
104
|
+
node.instance_of?(Prism::DefNode) && nesting.positive? && method_mutation?(node)
|
|
105
|
+
end
|
|
106
|
+
private_class_method :block_defined_method_mutation?
|
|
107
|
+
|
|
108
|
+
# True when `node`, a `def` or anything in its body, holds a call or index write
|
|
109
|
+
# that mutates an instance variable, class variable or global in place: a {MutationWidening::SHAPE_MUTATORS}
|
|
110
|
+
# name, or an index `||=` / `&&=` / `op=`, on a read of one. A class or module body inside the method is not
|
|
111
|
+
# looked into; a `def` nested in it is.
|
|
112
|
+
def method_mutation?(node)
|
|
113
|
+
klass = node.class
|
|
114
|
+
if klass == Prism::CallNode
|
|
115
|
+
return true if MutationWidening::SHAPE_MUTATORS.include?(node.name) && non_local_read?(node.receiver)
|
|
116
|
+
elsif OUTLIVING_WRITE_NODES.include?(klass)
|
|
117
|
+
return true if node.respond_to?(:receiver) && non_local_read?(node.receiver)
|
|
118
|
+
elsif klass != Prism::DefNode && OPAQUE_NODES.include?(klass)
|
|
119
|
+
return false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
found = false
|
|
123
|
+
node.rigor_each_child { |child| found ||= method_mutation?(child) }
|
|
124
|
+
found
|
|
125
|
+
end
|
|
126
|
+
private_class_method :method_mutation?
|
|
127
|
+
|
|
128
|
+
def non_local_read?(node)
|
|
129
|
+
NON_LOCAL_READ_NODES.include?(node.class)
|
|
130
|
+
end
|
|
131
|
+
private_class_method :non_local_read?
|
|
132
|
+
|
|
92
133
|
# True when `node` is a call the straight-line widening would answer for a variable that outlives the
|
|
93
134
|
# expression: a {MutationWidening::SHAPE_MUTATORS} name on a receiver whose {ReceiverAlias.mutated_reads},
|
|
94
135
|
# or the local an element read is rooted at ({ElementReadWidening.element_read_path}, `a[0] << e`), reach
|