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
|
@@ -6,6 +6,7 @@ require_relative "../source/constant_path"
|
|
|
6
6
|
require_relative "../source/node_children"
|
|
7
7
|
require_relative "refine_census"
|
|
8
8
|
require_relative "refine_self"
|
|
9
|
+
require_relative "../typing_state"
|
|
9
10
|
require_relative "in_effect_refinements/proc_literals"
|
|
10
11
|
require_relative "in_effect_refinements/scope_reads"
|
|
11
12
|
require_relative "in_effect_refinements/refine_defs"
|
|
@@ -53,6 +54,12 @@ module Rigor
|
|
|
53
54
|
#
|
|
54
55
|
# Built lazily: the walk runs the first time a consumer asks, so a file nobody asks about pays one small object.
|
|
55
56
|
class InEffectRefinements
|
|
57
|
+
# ADR-129 G4 — {#build} fills every one of these once, from the file's root node alone.
|
|
58
|
+
TypingState.memo_field(self, :@built, :@activations, :@refinement_defs, :@refine_defs, :@chained_refined_calls,
|
|
59
|
+
:@class_body_refines, :@refine_contexts, :@literal_bindings, :@refine_offsets,
|
|
60
|
+
:@unresolved_using, :@nesting,
|
|
61
|
+
reason: "built once by #build from the file's root node alone")
|
|
62
|
+
|
|
56
63
|
# The marker an activation contributes when it may put any refinement in effect: a non-constant `using`, a
|
|
57
64
|
# `using` whose include expansion is unknown, or a `refine` block whose module this walk cannot name. A list
|
|
58
65
|
# that carries it answers "any refinement may be in effect"; its position carries no meaning.
|
|
@@ -9,8 +9,7 @@ require_relative "unknown_store_widening"
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Inference
|
|
11
11
|
# Closes the "`params[:f] ||= []; params[:f] << x`" precision gap surfaced by the Redmine
|
|
12
|
-
# 6.1.2 `Query#as_params` survey
|
|
13
|
-
# "Indexed-collection narrowing through `Hash[k] ||= default`").
|
|
12
|
+
# 6.1.2 `Query#as_params` survey.
|
|
14
13
|
#
|
|
15
14
|
# After `receiver[key] ||= default` the next read at `receiver[key]` is known non-nil, but
|
|
16
15
|
# Rigor types each `Hash#[]` independently and the subsequent `<<` / `[]=` / other mutator
|
|
@@ -34,8 +33,10 @@ module Rigor
|
|
|
34
33
|
#
|
|
35
34
|
# **Invalidation.** Three conditions drop a recorded narrowing:
|
|
36
35
|
# - The receiver variable is rebound (handled inside `Scope#with_local` / `Scope#with_ivar`).
|
|
37
|
-
# - An intervening
|
|
38
|
-
#
|
|
36
|
+
# - An intervening store writes the same slot — `receiver[key] = value`, an index target, or a
|
|
37
|
+
# compound `receiver[key] op= value` / `&&=` (issue #1218) — since it could rebind the slot to
|
|
38
|
+
# nil; conservative drop. A store whose index is not one literal key drops every narrowing
|
|
39
|
+
# on the receiver.
|
|
39
40
|
# - An intervening mutator from {MutationWidening::SHAPE_MUTATORS} runs against the receiver
|
|
40
41
|
# (e.g. `params.delete(:f)`, `params.clear`, `params.default = 0`, `buf.delete_prefix!("x")`).
|
|
41
42
|
#
|
|
@@ -139,14 +140,61 @@ module Rigor
|
|
|
139
140
|
MutationWidening::SHAPE_MUTATORS.include?(method_name)
|
|
140
141
|
end
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
# Drops the narrowing on the slot a store through `[]=` overwrote. `node` is the `[]=` call, an index target
|
|
144
|
+
# (a multi-assign slot, a `for` index, a rescue reference) or a compound index write (`h[k] += v`,
|
|
145
|
+
# `h[k] &&= v`, `h[k] ||= v`); every form reads only `receiver` and `arguments`, and only a `[]=` call's last
|
|
146
|
+
# argument is the stored value rather than an index.
|
|
147
|
+
#
|
|
148
|
+
# Issue #1218 — a literal single index drops that one slot and leaves the others. Any other index list on a
|
|
149
|
+
# stable receiver — a key that is not a literal (`h[k] = 1` may be `h[:e] = 1`), a multi-index splice
|
|
150
|
+
# (`s[0, 1] = [1, 2]` shifts every later slot), a splat — may write any slot, so it drops every narrowing on
|
|
151
|
+
# the receiver, as {.invalidate_mutator} does. So does a negative Integer index on a receiver not shown to be a
|
|
152
|
+
# `Hash`: on an `Array` `a[-1]` is the slot some non-negative index also names. An unstable receiver records
|
|
153
|
+
# no narrowing to drop.
|
|
154
|
+
def invalidate_indexed_write(node, current_scope)
|
|
155
|
+
receiver = stable_receiver(node.receiver)
|
|
156
|
+
return current_scope if receiver.nil?
|
|
145
157
|
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
indexes = index_arguments(node)
|
|
159
|
+
address = indexes.size == 1 ? stable_address(node.receiver, indexes.first) : nil
|
|
160
|
+
if address && !(negative_index?(address.last) && !hash_receiver?(current_scope.type_of(node.receiver)))
|
|
161
|
+
return current_scope.without_indexed_narrowing(*address)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
current_scope.without_indexed_narrowings_for(*receiver)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Whether the literal `key` may name the same slot as another literal key: a negative Integer, unless the
|
|
168
|
+
# receiver is a `Hash`, which tells `-1` from every other key.
|
|
169
|
+
def aliasing_index?(key, receiver_type)
|
|
170
|
+
negative_index?(key) && !hash_receiver?(receiver_type)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def negative_index?(key) = key.is_a?(Integer) && key.negative?
|
|
174
|
+
|
|
175
|
+
def hash_receiver?(receiver_type) = carrier_receiver_type?(receiver_type, HASH_CARRIER)
|
|
176
|
+
|
|
177
|
+
VERBATIM_STORE_CARRIERS = %w[Hash Array].freeze
|
|
178
|
+
HASH_CARRIER = %w[Hash].freeze
|
|
179
|
+
private_constant :VERBATIM_STORE_CARRIERS, :HASH_CARRIER
|
|
180
|
+
|
|
181
|
+
# Whether a store through `[]=` on a receiver of `receiver_type` at the literal `key` leaves exactly the stored
|
|
182
|
+
# value at that slot, so a recorded narrowing may claim it. Only a `Hash` or `Array` carrier (a shape, a tuple,
|
|
183
|
+
# a `Hash` / `Array` nominal, refined or not) does, for every member of a union. A `String`'s `[]=` splices
|
|
184
|
+
# (`s[0] = "xy"` leaves `s[0] == "x"`), and a project class's `[]=` may store anything. A negative Integer key
|
|
185
|
+
# aliases a non-negative one on an `Array` ({.aliasing_index?}), so it records only on a `Hash`.
|
|
186
|
+
def verbatim_slot?(receiver_type, key)
|
|
187
|
+
carrier_receiver_type?(receiver_type, VERBATIM_STORE_CARRIERS) && !aliasing_index?(key, receiver_type)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def carrier_receiver_type?(type, classes)
|
|
191
|
+
members = type.is_a?(Type::Union) ? type.members : [type]
|
|
192
|
+
members.all? { |member| classes.include?(UnknownStoreWidening.carrier_class(member)) }
|
|
193
|
+
end
|
|
148
194
|
|
|
149
|
-
|
|
195
|
+
def index_arguments(node)
|
|
196
|
+
args = node.arguments&.arguments || []
|
|
197
|
+
node.is_a?(Prism::CallNode) ? args[0...-1] : args
|
|
150
198
|
end
|
|
151
199
|
|
|
152
200
|
def invalidate_mutator(call_node, current_scope)
|
|
@@ -206,11 +254,9 @@ module Rigor
|
|
|
206
254
|
args&.size == 1 ? stable_address(node.receiver, args.first) : nil
|
|
207
255
|
end
|
|
208
256
|
|
|
209
|
-
# Companion invalidator for single-hop method-chain narrowings
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
# receiver — matching the ROADMAP's "any intervening method call against the same
|
|
213
|
-
# receiver" criterion. A call against `x.last` (the OUTER receiver is a `CallNode`, not a
|
|
257
|
+
# Companion invalidator for single-hop method-chain narrowings. Drops every `(receiver, *)`
|
|
258
|
+
# chain narrowing rooted at the call's OUTER stable receiver, for any intervening method call
|
|
259
|
+
# against the same receiver. A call against `x.last` (the OUTER receiver is a `CallNode`, not a
|
|
214
260
|
# stable root) does NOT drop narrowings keyed on `x`, so the worked-site `x.last << y`
|
|
215
261
|
# pattern correctly preserves the chain narrowing for any further `x.last` read in the same
|
|
216
262
|
# body. Always-safe (only forgets; never invents).
|
|
@@ -19,8 +19,8 @@ module Rigor
|
|
|
19
19
|
# the calls in a condition operand, where a narrowing an earlier operand or clause made would otherwise survive
|
|
20
20
|
# them: `if @x.nil? && reset!` read `@x` as `nil` in the body although `reset!` assigns it.
|
|
21
21
|
module InterveningIvarReset
|
|
22
|
-
# Nodes whose body does not run where it is written, or runs on another `self`: a `def`, and a class
|
|
23
|
-
# module body, where `@x` is the class's own instance variable.
|
|
22
|
+
# Nodes whose body does not run where it is written, or runs on another `self`: a `def`, and a class,
|
|
23
|
+
# module or `class << …` body, where `@x` is the class's own instance variable.
|
|
24
24
|
NON_RUNNING_NODES = [Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode].freeze
|
|
25
25
|
# Issue #1402 — the nodes that run code the program does not show on `self`'s behalf: `yield` runs the
|
|
26
26
|
# method's block, and `super` the superclass's method on `self`.
|
|
@@ -180,12 +180,17 @@ module Rigor
|
|
|
180
180
|
# takes the mark when widened, so a `nil` only the seed brings back is not fuel for
|
|
181
181
|
# `call.possible-nil-receiver` (ADR-58), as a declared one is not. A narrowing that kept `nil`
|
|
182
182
|
# (`return if @tag`, `@tag.nil?`) widens unmarked, so a read the program really makes on `nil` still reports.
|
|
183
|
-
|
|
183
|
+
# Issue #2039 — `mark: :unproven` marks as `mark: true` does only when the block answers nil (no write set is
|
|
184
|
+
# proven) and `direct_writes` (called at most once, answering a `Set`) does not name the variable: a call
|
|
185
|
+
# proven to write it, or whose callee visibly assigns it a possibly-nil value ({DirectIvarWrites}), widens it
|
|
186
|
+
# unmarked.
|
|
187
|
+
def widen(scope, mark: false, direct_writes: nil)
|
|
184
188
|
seed = class_ivar_seed(scope)
|
|
185
189
|
return scope if seed.nil? || seed.empty?
|
|
186
190
|
|
|
187
191
|
computed = false
|
|
188
192
|
writes = nil
|
|
193
|
+
direct = lazy_set(direct_writes)
|
|
189
194
|
seed.reduce(scope) do |widened, (name, seed_type)|
|
|
190
195
|
local_type = scope.ivar(name)
|
|
191
196
|
next widened if local_type.nil? || local_type == seed_type
|
|
@@ -198,11 +203,28 @@ module Rigor
|
|
|
198
203
|
|
|
199
204
|
joined = Type::Combinator.union(local_type, seed_type)
|
|
200
205
|
widened = widened.with_ivar(name, joined)
|
|
201
|
-
marked = (mark
|
|
206
|
+
marked = widen_marks?(mark, writes, direct, name, local_type) || scope.declaration_sourced?(:ivar, name)
|
|
202
207
|
marked ? widened.seed_declaration_sourced_ivar(name, joined) : widened
|
|
203
208
|
end
|
|
204
209
|
end
|
|
205
210
|
|
|
211
|
+
# Whether {.widen} stamps the declaration mark on `name`'s binding, narrowed to `local_type`: one whose
|
|
212
|
+
# narrowing ruled `nil` out, under `mark: true`, or under `mark: :unproven` when no write set is proven
|
|
213
|
+
# (`writes` nil) and the callee does not visibly write it (`direct`).
|
|
214
|
+
def widen_marks?(mark, writes, direct, name, local_type)
|
|
215
|
+
return false unless mark && nil_free?(local_type)
|
|
216
|
+
|
|
217
|
+
mark != :unproven || (writes.nil? && !direct.call.include?(name))
|
|
218
|
+
end
|
|
219
|
+
private_class_method :widen_marks?
|
|
220
|
+
|
|
221
|
+
# A callable answering `callable`'s `Set`, computed on the first call; an empty one when `callable` is nil.
|
|
222
|
+
def lazy_set(callable)
|
|
223
|
+
value = nil
|
|
224
|
+
-> { value ||= callable ? callable.call : Set.new }
|
|
225
|
+
end
|
|
226
|
+
private_class_method :lazy_set
|
|
227
|
+
|
|
206
228
|
# Issue #1963 — `scope` past the calls `node`, a condition operand, makes: {.widen} over the names every
|
|
207
229
|
# candidate call in it may write, or every name when one of them is not proven. A call's literal block may run,
|
|
208
230
|
# so its calls count; a `def`, a class or module body and (unless `run_lambdas`) a lambda literal run nothing
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
+
require_relative "census_foreign_block"
|
|
6
|
+
require_relative "receiver_writer_call"
|
|
7
|
+
|
|
5
8
|
module Rigor
|
|
6
9
|
module Inference
|
|
7
10
|
# Issue #1154 — the instance-variable writers {GlobalWriteCensus} records for the declared-parameter credit
|
|
@@ -22,6 +25,17 @@ module Rigor
|
|
|
22
25
|
# - `[:own_write, class_name, name]` — an `@x` write inside an instance body of the class.
|
|
23
26
|
# {ScopeIndexer#merge_constant_literal_tables} appends the file's path, so a reader can tell whether a second
|
|
24
27
|
# file writes the same ivar of the same class.
|
|
28
|
+
# - `[:own_write_type, class_name, name, value]` — issues #2079 / #2170: the same write with its coarse value, a
|
|
29
|
+
# literal's as `[:lit, value]` and anything else as {DYNAMIC}, and a class-body `attr_writer` /
|
|
30
|
+
# `attr_accessor` of the name as {DYNAMIC}. The path is appended as for `:own_write`. Issue #2488: an
|
|
31
|
+
# explicit-receiver `E.attr_writer` / `E.attr_accessor` (or one through `send`) records the same {DYNAMIC}
|
|
32
|
+
# entry under `E`, and an `E.define_method` block is an instance body of `E` ({ReceiverWriterCall}). The
|
|
33
|
+
# class-ivar census of a file joins another file's entries, and those of the modules and superclasses on the
|
|
34
|
+
# class's chain, into the ivars it already holds ({ScopeIndexer::IvarContributions}).
|
|
35
|
+
# Issue #2983: a barred `class_eval`-family block on a receiver rooted at another constant files the code in it
|
|
36
|
+
# that runs on an instance under that constant's candidate key ({#remember_root_constant}), a String
|
|
37
|
+
# `class_eval` records a {DYNAMIC} write of {ANY_IVAR} ({#record_string_eval}), and a constant write `C = K` /
|
|
38
|
+
# `C = K.new` records `[:binds_class, key, "C"]` ({#record_binding}), so the join can take `C`'s entries for `K`.
|
|
25
39
|
class IvarWriterCensus
|
|
26
40
|
SEND_CALLS = %i[send __send__ public_send].to_set.freeze
|
|
27
41
|
# The methods a `send` reaching which is an open-world writer.
|
|
@@ -34,6 +48,14 @@ module Rigor
|
|
|
34
48
|
attr_writer attr_accessor send public_send __send__
|
|
35
49
|
].to_set.freeze
|
|
36
50
|
NO_ARGUMENTS = [].freeze
|
|
51
|
+
# Issue #2079 — the coarse value of a write that is not a literal.
|
|
52
|
+
DYNAMIC = :dynamic
|
|
53
|
+
# Issue #2983 — the ivar name of a write whose ivar no literal spells: a string `class_eval`'s code may write any.
|
|
54
|
+
ANY_IVAR = :*
|
|
55
|
+
# The `class_eval`-family calls that take code in a String.
|
|
56
|
+
STRING_EVALS = %i[class_eval module_eval].to_set.freeze
|
|
57
|
+
# The writer declarations a class body records as a {DYNAMIC} write of each name it spells.
|
|
58
|
+
WRITER_DECLARATIONS = %i[attr_writer attr_accessor].to_set.freeze
|
|
37
59
|
|
|
38
60
|
# The call names any rule here reads, so every other call returns at one Set probe.
|
|
39
61
|
# (`define_method` is in {SINGLETON_CLASS_CALLS}.)
|
|
@@ -44,16 +66,23 @@ module Rigor
|
|
|
44
66
|
@instance_ranges = []
|
|
45
67
|
@owner_names = {}.compare_by_identity
|
|
46
68
|
@own_writes = {}
|
|
69
|
+
@typed_writes = Set.new
|
|
70
|
+
@rooted = Set.new.compare_by_identity
|
|
47
71
|
end
|
|
48
72
|
|
|
49
73
|
# Fed each node of a pre-order descent. `owner` is the class whose body the node sits in, as its qualified name
|
|
50
74
|
# or as the host walk's prefix Array (joined here only when an entry needs it), or nil where no class owns an
|
|
51
|
-
# instance (`class << self`, the top level, an opaque block).
|
|
52
|
-
|
|
75
|
+
# instance (`class << self`, the top level, an opaque block). `receiver_owner` is the class an explicit-receiver
|
|
76
|
+
# writer call ({ReceiverWriterCall}) names, resolved by the host walk as it resolves a `class_eval` receiver.
|
|
77
|
+
def visit(node, owner, receiver_owner = nil)
|
|
53
78
|
case node
|
|
54
|
-
when Prism::DefNode then
|
|
79
|
+
when Prism::DefNode then remember_host(node, owner) if node.receiver.nil?
|
|
80
|
+
when Prism::ConstantWriteNode, Prism::ConstantPathWriteNode then record_binding(node, owner)
|
|
55
81
|
when Prism::SingletonClassNode then visit_singleton_class(node)
|
|
56
|
-
when Prism::CallNode
|
|
82
|
+
when Prism::CallNode
|
|
83
|
+
bar_foreign_block(node)
|
|
84
|
+
visit_call(node, owner) if WATCHED_CALLS.include?(node.name)
|
|
85
|
+
record_receiver_writers(node, receiver_owner) if receiver_owner
|
|
57
86
|
when Prism::InstanceVariableWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode,
|
|
58
87
|
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableTargetNode
|
|
59
88
|
record_own_write(node) unless @instance_ranges.empty?
|
|
@@ -67,6 +96,7 @@ module Rigor
|
|
|
67
96
|
owner = instance_owner(node)
|
|
68
97
|
return if owner.nil?
|
|
69
98
|
|
|
99
|
+
record_typed_write(owner, node.name, coarse_value(node))
|
|
70
100
|
seen = (@own_writes[owner] ||= {})
|
|
71
101
|
return if seen[node.name]
|
|
72
102
|
|
|
@@ -74,10 +104,119 @@ module Rigor
|
|
|
74
104
|
@census << [:own_write, owner_name(owner), node.name].freeze
|
|
75
105
|
end
|
|
76
106
|
|
|
107
|
+
def record_typed_write(owner, name, value)
|
|
108
|
+
entry = [:own_write_type, owner_name(owner), name, value].freeze
|
|
109
|
+
@census << entry if @typed_writes.add?(entry)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Issue #2079 — a literal's value as `[:lit, value]`; any other value, an operator write and a multiple
|
|
113
|
+
# assignment target as {DYNAMIC}.
|
|
114
|
+
def coarse_value(node)
|
|
115
|
+
value = node.value if node.respond_to?(:value) && !node.is_a?(Prism::InstanceVariableOperatorWriteNode)
|
|
116
|
+
case value
|
|
117
|
+
when Prism::NilNode then [:lit, nil].freeze
|
|
118
|
+
when Prism::TrueNode then [:lit, true].freeze
|
|
119
|
+
when Prism::FalseNode then [:lit, false].freeze
|
|
120
|
+
when Prism::IntegerNode, Prism::FloatNode then [:lit, value.value].freeze
|
|
121
|
+
when Prism::SymbolNode, Prism::StringNode then literal_text(value)
|
|
122
|
+
else DYNAMIC
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def literal_text(node)
|
|
127
|
+
text = node.unescaped
|
|
128
|
+
return DYNAMIC unless text.valid_encoding?
|
|
129
|
+
|
|
130
|
+
[:lit, node.is_a?(Prism::SymbolNode) ? text.to_sym : text.dup.freeze].freeze
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Issue #2490 — a block inside an instance body whose `self` is another object ({CensusForeignBlock}) is
|
|
134
|
+
# remembered with no owner, so its writes credit nothing. Pre-order, so an instance body nested in it (an
|
|
135
|
+
# `E.define_method` block, remembered after) still answers for its own class.
|
|
136
|
+
def bar_foreign_block(node)
|
|
137
|
+
return if @instance_ranges.empty? || !CensusForeignBlock::CALLS.include?(node.name)
|
|
138
|
+
|
|
139
|
+
instance = instance_owner(node)
|
|
140
|
+
return unless instance && CensusForeignBlock.foreign?(node, owner_name(instance))
|
|
141
|
+
|
|
142
|
+
remember(node.block.location, nil)
|
|
143
|
+
remember_reentry(node, instance)
|
|
144
|
+
remember_root_constant(node, instance)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Issue #2983 — the code a barred `class_eval`-family block on a receiver rooted at another constant reenters
|
|
148
|
+
# (`Anc.class_eval`, `INST.class.class_eval`, `AliasK.new.class.class_eval`), remembered under that constant's
|
|
149
|
+
# candidate key ({CensusForeignBlock.root_constant}), in place of the host walk's as-written owner. A class
|
|
150
|
+
# whose census joins the entries of the constant the key resolves to, an ancestor's, an alias's or an instance
|
|
151
|
+
# constant's ({ScopeIndexer::IvarContributions}), then sees the write.
|
|
152
|
+
def remember_root_constant(node, instance)
|
|
153
|
+
root, blocks = CensusForeignBlock.rooted_reentry(node, owner_name(instance))
|
|
154
|
+
blocks&.each { |block| remember(block.location, root) }
|
|
155
|
+
@rooted.merge(blocks) if blocks
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# The host walk's `owner` of a `def` or `define_method` block, unless {#remember_root_constant} filed it already.
|
|
159
|
+
def remember_host(node, owner) = (remember(node.location, owner) if owner && !@rooted.include?(node))
|
|
160
|
+
|
|
161
|
+
# Issue #2983 — `C = K` or `C = K.new` (`.allocate`) records `[:binds_class, key, "C"]`, `K`'s candidate key and
|
|
162
|
+
# `C`'s qualified name ({CensusForeignBlock.binding_entry}), so a class's census also joins the entries filed
|
|
163
|
+
# under an alias or an instance constant of it (`AliasK.class_eval { … }`, `INST.class.class_eval { … }`).
|
|
164
|
+
def record_binding(node, owner)
|
|
165
|
+
entry = CensusForeignBlock.binding_entry(node, nesting(owner))
|
|
166
|
+
@census << entry if entry
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# The lexical class and module names `owner` spells: the host walk's prefix Array, or a resolved name.
|
|
170
|
+
def nesting(owner) = owner.nil? ? NO_ARGUMENTS : owner_name(owner).split("::")
|
|
171
|
+
|
|
172
|
+
# Issue #2983 — `class_eval` / `module_eval` given a String runs code the census cannot read, which may define
|
|
173
|
+
# a method writing any ivar: a {DYNAMIC} write of {ANY_IVAR} is recorded for the module the receiver may be. That
|
|
174
|
+
# is the class owning the body for an implicit or `self` receiver in a class body, the instance's class for a
|
|
175
|
+
# receiver in an instance body that {CensusForeignBlock.foreign_module?} does not bar, and otherwise the constant
|
|
176
|
+
# the receiver chain is rooted at.
|
|
177
|
+
def record_string_eval(node, owner)
|
|
178
|
+
return unless STRING_EVALS.include?(node.name) && node.block.nil? && node.arguments
|
|
179
|
+
|
|
180
|
+
instance = instance_owner(node) unless @instance_ranges.empty?
|
|
181
|
+
target = CensusForeignBlock.string_eval_target(node, owner_name(instance), owner_name(owner))
|
|
182
|
+
record_typed_write(target, ANY_IVAR, DYNAMIC) if target
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The `define_method` blocks in a barred body that run on the instance again, remembered after the bar so they
|
|
186
|
+
# answer for the instance's class ({CensusForeignBlock.reentry_blocks}).
|
|
187
|
+
def remember_reentry(node, instance)
|
|
188
|
+
CensusForeignBlock.reentry_blocks(node, owner_name(instance)).each do |block|
|
|
189
|
+
remember(block.location, instance)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Issue #2488 — `E.attr_writer :w` declares a writer of `@w` on `E`, and `E.define_method(:m) { … }` installs an
|
|
194
|
+
# instance body of `E`, wherever the call sits.
|
|
195
|
+
def record_receiver_writers(node, class_name)
|
|
196
|
+
ReceiverWriterCall.writer_ivars(node).each { |ivar| record_typed_write(class_name, ivar, DYNAMIC) }
|
|
197
|
+
block = ReceiverWriterCall.define_method_block(node)
|
|
198
|
+
remember(block.location, class_name) if block
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Issue #2079 — `attr_writer :x` / `attr_accessor :x` in a class body writes `@x` with whatever the caller hands.
|
|
202
|
+
def record_writer_declaration(node, owner)
|
|
203
|
+
return unless owner && WRITER_DECLARATIONS.include?(node.name) && self_receiver?(node)
|
|
204
|
+
# A guard: an instance body's own `attr_writer` call raises (the method is `Module`'s), so only the class
|
|
205
|
+
# body's declares a writer.
|
|
206
|
+
return unless instance_owner(node).nil?
|
|
207
|
+
|
|
208
|
+
(node.arguments&.arguments || NO_ARGUMENTS).each do |argument|
|
|
209
|
+
name = literal_name(argument)
|
|
210
|
+
record_typed_write(owner, :"@#{name}", DYNAMIC) if name
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
77
214
|
def owner_name(owner) = owner.is_a?(Array) ? (@owner_names[owner] ||= owner.join("::")) : owner
|
|
78
215
|
|
|
79
216
|
def visit_call(node, owner)
|
|
80
|
-
|
|
217
|
+
record_writer_declaration(node, owner)
|
|
218
|
+
record_string_eval(node, owner)
|
|
219
|
+
remember_host(node.block, owner) if define_method_block?(node)
|
|
81
220
|
if self_receiver?(node) || own_singleton_class_call?(node)
|
|
82
221
|
return if @instance_ranges.empty? || !(own_singleton_class_call?(node) || open_world?(node))
|
|
83
222
|
|
|
@@ -88,12 +227,19 @@ module Rigor
|
|
|
88
227
|
end
|
|
89
228
|
end
|
|
90
229
|
|
|
91
|
-
# `class << self` inside an instance `def` opens the instance's singleton class.
|
|
230
|
+
# `class << self` inside an instance `def` opens the instance's singleton class. Its body's `self` is that
|
|
231
|
+
# singleton class, so an `@x` write there is the singleton's own ivar (#2490), whatever the expression.
|
|
92
232
|
def visit_singleton_class(node)
|
|
93
|
-
return
|
|
233
|
+
return if @instance_ranges.empty?
|
|
94
234
|
|
|
95
235
|
instance = instance_owner(node)
|
|
96
|
-
|
|
236
|
+
return unless instance
|
|
237
|
+
|
|
238
|
+
record_class(instance) if node.expression.is_a?(Prism::SelfNode)
|
|
239
|
+
return unless node.body
|
|
240
|
+
|
|
241
|
+
remember(node.body.location, nil)
|
|
242
|
+
remember_reentry(node, instance)
|
|
97
243
|
end
|
|
98
244
|
|
|
99
245
|
def record_class(owner) = @census << [:ivar_set_class, owner_name(owner)].freeze
|
|
@@ -5,6 +5,8 @@ require "prism"
|
|
|
5
5
|
require_relative "../type"
|
|
6
6
|
require_relative "mutation_widening"
|
|
7
7
|
require_relative "key_presence_guard/scan"
|
|
8
|
+
require_relative "../typing_state"
|
|
9
|
+
require_relative "../memo_audit"
|
|
8
10
|
|
|
9
11
|
module Rigor
|
|
10
12
|
module Inference
|
|
@@ -57,9 +59,22 @@ module Rigor
|
|
|
57
59
|
] + MutationWidening::SHAPE_MUTATORS.to_a
|
|
58
60
|
).to_set.freeze
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
# ADR-129 WD1 — the walk {.with_guards} installs is a Frame, and {.record}, which rewrites its `guard_ends` in
|
|
63
|
+
# place, is a Resource touch that also takes a fresh id ({TypingState::State#rewrite}), so a read of the walk's
|
|
64
|
+
# guards is fixed while the id is. The walk's `defs`, `frozen_constants` and `memo` are extensional caches of
|
|
65
|
+
# `root`, filled in place without a touch.
|
|
66
|
+
WALK = TypingState.frame(
|
|
67
|
+
:key_presence_walk,
|
|
68
|
+
contents: :resource, read_exemption: :id_determined,
|
|
69
|
+
reason: "installed per guarded re-walk; each recorded guard rewrites its `guard_ends`",
|
|
70
|
+
witness: "spec/fixtures/typing_state_witnesses/key_presence_walk.rb"
|
|
71
|
+
)
|
|
72
|
+
REWALK = TypingState.memo(
|
|
73
|
+
:key_presence_rewalk,
|
|
74
|
+
run_scoped: true, audit: "key presence rewalk",
|
|
75
|
+
reason: "the last file's guarded index, keyed by the file's root"
|
|
76
|
+
)
|
|
77
|
+
private_constant :WALK, :REWALK
|
|
63
78
|
|
|
64
79
|
# The re-walk's state: the file's root, the end offset of each recorded guard, and the per-file scans.
|
|
65
80
|
Walk = Struct.new(:root, :guard_ends, :defs, :frozen_constants, :memo) do
|
|
@@ -87,25 +102,21 @@ module Rigor
|
|
|
87
102
|
|
|
88
103
|
# Runs the block with guards recorded and read, for the re-walk of `root`. Thread-local, so it holds under a
|
|
89
104
|
# Ractor worker too.
|
|
90
|
-
def with_guards(root)
|
|
91
|
-
|
|
92
|
-
Thread.current[STATE_KEY] = Walk.new(root, {}, nil, nil, {})
|
|
93
|
-
yield
|
|
94
|
-
ensure
|
|
95
|
-
Thread.current[STATE_KEY] = previous
|
|
105
|
+
def with_guards(root, &)
|
|
106
|
+
TypingState.current.with(WALK, Walk.new(root, {}, nil, nil, {}), &)
|
|
96
107
|
end
|
|
97
108
|
|
|
98
109
|
# Runs the block with guards off again: a callee's return summary, memoised for the whole run, must be the one
|
|
99
110
|
# the file's analysis reads.
|
|
100
|
-
def without_guards
|
|
101
|
-
|
|
102
|
-
Thread.current[STATE_KEY] = nil
|
|
103
|
-
yield
|
|
104
|
-
ensure
|
|
105
|
-
Thread.current[STATE_KEY] = previous
|
|
111
|
+
def without_guards(&)
|
|
112
|
+
TypingState.current.with(WALK, nil, &)
|
|
106
113
|
end
|
|
107
114
|
|
|
108
|
-
def active? = !
|
|
115
|
+
def active? = !TypingState.current[WALK].nil?
|
|
116
|
+
|
|
117
|
+
# {.active?}, read only to decide whether to turn the walk off for the extent that follows ({.without_guards}):
|
|
118
|
+
# that extent reads it off either way, so the key audit does not count the read (ADR-129 WD1).
|
|
119
|
+
def forcing_off? = !TypingState.current.forcing_read(WALK).nil?
|
|
109
120
|
|
|
110
121
|
# Whether `call.possible-nil-receiver` should withhold its report on `call_node`, whose receiver the file's
|
|
111
122
|
# analysis types `receiver_type`: true only when the guarded re-walk types that receiver as exactly
|
|
@@ -129,19 +140,33 @@ module Rigor
|
|
|
129
140
|
# The guarded scope index of `root`, built once per file (the cache holds the last file only), or nil when the
|
|
130
141
|
# file holds no guard-shaped call.
|
|
131
142
|
def rewalk(root, scope_index)
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
return guarded_index(root, scope_index) if MemoAudit.enabled? && MemoAudit.suspended?
|
|
144
|
+
|
|
145
|
+
state = TypingState.current
|
|
146
|
+
cached = state[REWALK]
|
|
147
|
+
if cached&.first.equal?(root)
|
|
148
|
+
# A re-walk that raised is stored as nil, and its audit recomputation answers nil the same way.
|
|
149
|
+
return MemoAudit.check("key presence rewalk", cached.last, root) do
|
|
150
|
+
guarded_index(root, scope_index)
|
|
151
|
+
rescue StandardError
|
|
152
|
+
nil
|
|
153
|
+
end
|
|
154
|
+
end
|
|
134
155
|
|
|
135
156
|
# Cached before the walk, so a re-walk that raises is attempted once per file and then answers nil.
|
|
136
|
-
|
|
157
|
+
state[REWALK] = [root, nil]
|
|
158
|
+
index = MemoAudit.key_audit("key presence rewalk") { guarded_index(root, scope_index) }
|
|
159
|
+
state[REWALK] = [root, index]
|
|
160
|
+
index
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def guarded_index(root, scope_index)
|
|
137
164
|
return nil unless guard_shaped_call?(root)
|
|
138
165
|
|
|
139
166
|
base = scope_index[root]
|
|
140
|
-
|
|
167
|
+
base && with_guards(root) do
|
|
141
168
|
ScopeIndexer.index(root, default_scope: base.with_isolated_side_tables)
|
|
142
169
|
end
|
|
143
|
-
Thread.current[CACHE_KEY] = [root, index]
|
|
144
|
-
index
|
|
145
170
|
end
|
|
146
171
|
|
|
147
172
|
def guard_shaped_call?(node)
|
|
@@ -205,7 +230,8 @@ module Rigor
|
|
|
205
230
|
# The truthy edge of `receiver.key?(key)` with a non-literal key, inside the re-walk: `scope` with the guard
|
|
206
231
|
# recorded, or nil when the guard does not apply.
|
|
207
232
|
def record(call_node, scope)
|
|
208
|
-
|
|
233
|
+
state = TypingState.current
|
|
234
|
+
walk = state[WALK]
|
|
209
235
|
return nil if walk.nil?
|
|
210
236
|
|
|
211
237
|
args = call_node.arguments&.arguments
|
|
@@ -216,6 +242,7 @@ module Rigor
|
|
|
216
242
|
return nil unless receiver_type?(scope.type_of(call_node.receiver))
|
|
217
243
|
return nil unless Scan.eligible?(walk, call_node, guard)
|
|
218
244
|
|
|
245
|
+
state.rewrite(WALK)
|
|
219
246
|
walk.guard_ends[guard] = call_node.location.end_offset
|
|
220
247
|
scope.with_indexed_narrowing(*guard, PRESENT)
|
|
221
248
|
end
|
|
@@ -225,7 +252,7 @@ module Rigor
|
|
|
225
252
|
# read may have changed it ({.intervening?}), and no value of the receiver can be `nil` (the read cannot tell a
|
|
226
253
|
# value's own `nil` from the miss). Nil when no guard applies or nothing would change.
|
|
227
254
|
def guarded_read(node, type, scope)
|
|
228
|
-
walk =
|
|
255
|
+
walk = TypingState.current[WALK]
|
|
229
256
|
guard = walk && held_guard(node, scope)
|
|
230
257
|
return nil if guard.nil? || Scan.intervening?(walk, guard, node)
|
|
231
258
|
|
|
@@ -4,6 +4,7 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../../source/constant_path"
|
|
6
6
|
require_relative "../../source/node_children"
|
|
7
|
+
require_relative "../../typing_state"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
9
10
|
module Inference
|
|
@@ -50,6 +51,9 @@ module Rigor
|
|
|
50
51
|
# `undef_method :gets`), other than `&:gets` and the first argument of a call that only names a method
|
|
51
52
|
# (`respond_to?(:gets)`, `send(:gets)`).
|
|
52
53
|
class SelfEvidence
|
|
54
|
+
TypingState.memo_field(self, :@scan, reason: "one walk of the file's root node, built on first ask")
|
|
55
|
+
TypingState.memo_field(self, :@const_set_spellings, reason: "one walk of the file's root, built on first ask")
|
|
56
|
+
|
|
53
57
|
# The context of a reader whose `self` is `main`.
|
|
54
58
|
MAIN = :main
|
|
55
59
|
UNKNOWN = :unknown
|
|
@@ -83,8 +87,14 @@ module Rigor
|
|
|
83
87
|
def initialize(root)
|
|
84
88
|
@root = root
|
|
85
89
|
@scan = nil
|
|
90
|
+
@const_set_spellings = nil
|
|
86
91
|
end
|
|
87
92
|
|
|
93
|
+
# Issue #3156 — the constants this file binds by a `const_set` with a literal name on the body's own module
|
|
94
|
+
# (implicit `self` or `self.const_set`) inside a `class` / `module` body, qualified by that body's nesting
|
|
95
|
+
# (`module NS; const_set(:AMid, Other)` binds `"NS::AMid"`). The census files no such write.
|
|
96
|
+
def const_set_spellings = (@const_set_spellings ||= collect_const_sets(@root, nil, Set.new).freeze)
|
|
97
|
+
|
|
88
98
|
# {MAIN}, the `Prism::ClassNode` or `Prism::ModuleNode` whose body the implicit-self reader `call_node` runs
|
|
89
99
|
# in, or nil: the file may give it another `self`, or it is not one of this file's readers.
|
|
90
100
|
def context(call_node) = scan.context(call_node)
|
|
@@ -96,6 +106,26 @@ module Rigor
|
|
|
96
106
|
|
|
97
107
|
def scan = (@scan ||= Scan.new(@root))
|
|
98
108
|
|
|
109
|
+
def collect_const_sets(node, namespace, found)
|
|
110
|
+
case node
|
|
111
|
+
when Prism::ClassNode, Prism::ModuleNode
|
|
112
|
+
inner = Source::ConstantPath.pushed_nesting(namespace ? [namespace] : [], node.constant_path)&.first
|
|
113
|
+
collect_const_sets(node.body, inner, found) if inner
|
|
114
|
+
return found
|
|
115
|
+
when Prism::CallNode
|
|
116
|
+
found << "#{namespace}::#{literal_const_set_name(node)}" if namespace && literal_const_set_name(node)
|
|
117
|
+
end
|
|
118
|
+
node&.compact_child_nodes&.each { |child| collect_const_sets(child, namespace, found) }
|
|
119
|
+
found
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def literal_const_set_name(node)
|
|
123
|
+
return nil unless node.name == :const_set && (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
|
|
124
|
+
|
|
125
|
+
name = node.arguments&.arguments&.first
|
|
126
|
+
name.unescaped if name.is_a?(Prism::SymbolNode) || name.is_a?(Prism::StringNode)
|
|
127
|
+
end
|
|
128
|
+
|
|
99
129
|
# One walk of the file, whose answers {SelfEvidence} keeps once the walk is complete.
|
|
100
130
|
class Scan
|
|
101
131
|
def initialize(root)
|
|
@@ -261,10 +261,13 @@ module Rigor
|
|
|
261
261
|
# The scope a block or lambda body enters with, as {MatchRebinding.block_entry} gives the match globals': `$_`
|
|
262
262
|
# forgotten when the body may set it, which a later iteration then reads, when the frame makes a closure that
|
|
263
263
|
# may, or when the owning call may: its receiver chain and arguments run before the method yields, and
|
|
264
|
-
# `IO.foreach` sets `$_` to each line it yields.
|
|
264
|
+
# `IO.foreach` sets `$_` to each line it yields. Issue #3195 — and whatever the body does when it runs later
|
|
265
|
+
# than where it is written ({FreshFrameBlocks.deferred_body?}): a proc's body, an enumerator's or a hash's
|
|
266
|
+
# default proc reads the `$_` the slot holds when it runs.
|
|
265
267
|
def block_entry(scope, block_node, call_node = nil)
|
|
266
268
|
return scope unless scope.last_line_bound?
|
|
267
|
-
return scope unless
|
|
269
|
+
return scope unless FreshFrameBlocks.deferred_body?(block_node, call_node, scope) ||
|
|
270
|
+
scope.last_line_closure? || may_set?(block_node.body, scope) || call_sets?(call_node, scope)
|
|
268
271
|
|
|
269
272
|
scope.forget_last_line
|
|
270
273
|
end
|