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
|
@@ -28,13 +28,12 @@ module Rigor
|
|
|
28
28
|
# a flag threaded into `Scope`: an ADR-2 plugin-facing bypass of dependency recording would produce
|
|
29
29
|
# a silently stale warm cache that no diagnostic diff can show.
|
|
30
30
|
#
|
|
31
|
-
# There is deliberately NO memo here.
|
|
32
|
-
# not exist yet, and a memo keyed on the tables this slice happens to name would be keyed on less
|
|
31
|
+
# There is deliberately NO memo here. A memo keyed on the tables the walk names would be keyed on less
|
|
33
32
|
# than the walk reads — the ancestor chain resolves names through `known_user_class?`
|
|
34
33
|
# (`discovered_def_nodes`, `discovered_methods`) and `ancestor_name_candidates`
|
|
35
34
|
# (`discovered_header_nestings`). The chain itself is memoised per discovery index
|
|
36
|
-
# ({Scope::ResolutionChain}); the
|
|
37
|
-
#
|
|
35
|
+
# ({Scope::ResolutionChain}); the dispatch callers in {MethodDispatcher::RbsDispatch} memoise their
|
|
36
|
+
# own answers, keyed on what they read.
|
|
38
37
|
module ExternalAncestorResolution
|
|
39
38
|
# The owner a name must PRECEDE for its declaration to win an MRO. See {declared_before_object?}.
|
|
40
39
|
OBJECT_OWNER = "Object"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../typing_state"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
module Inference
|
|
5
7
|
# Thread-local event recorder behind `rigor trace`: while a block runs under {record}, the inference engine
|
|
@@ -13,8 +15,14 @@ module Rigor
|
|
|
13
15
|
# hot paths (`ExpressionTyper#type_of`, `Scope#with_local`, `Type::Combinator.union`, `MethodDispatcher.dispatch`)
|
|
14
16
|
# each guard their emit behind {active?}, so a normal (non-tracing) run pays one integer comparison.
|
|
15
17
|
module FlowTracer
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
# ADR-129 WD1 — the installed recorder is a Frame whose contents are a Resource: each emit into it touches. It is
|
|
19
|
+
# an output the engine never reads back, and the activation count below is a process switch, outside the typing
|
|
20
|
+
# state (WD5). G1′: a tracer switch, under which the `type_of` memo stays shut (WD4).
|
|
21
|
+
RECORDER = TypingState.frame(
|
|
22
|
+
:flow_tracer, contents: :resource, reason: "the recorder a trace installs; each emit records into it",
|
|
23
|
+
memo_shut_while_installed: true
|
|
24
|
+
)
|
|
25
|
+
private_constant :RECORDER
|
|
18
26
|
|
|
19
27
|
# One animation-relevant moment.
|
|
20
28
|
#
|
|
@@ -95,15 +103,16 @@ module Rigor
|
|
|
95
103
|
# Activates recording on the current thread for the duration of the block and returns the frozen event list.
|
|
96
104
|
# Nests safely; restores the previous recorder on exit.
|
|
97
105
|
def record
|
|
98
|
-
previous = Thread.current[KEY]
|
|
99
106
|
recorder = Recorder.new
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
TypingState.current.with(RECORDER, recorder) do
|
|
108
|
+
@mutex.synchronize { @active_count += 1 }
|
|
109
|
+
begin
|
|
110
|
+
yield
|
|
111
|
+
recorder.events.freeze
|
|
112
|
+
ensure
|
|
113
|
+
@mutex.synchronize { @active_count -= 1 }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
107
116
|
end
|
|
108
117
|
|
|
109
118
|
# Plain integer read (GVL-atomic) — the disabled fast path.
|
|
@@ -114,20 +123,28 @@ module Rigor
|
|
|
114
123
|
# Brackets one expression-typing recursion. Falls through to the bare block when the current thread is not
|
|
115
124
|
# recording (another thread may have flipped {active?}).
|
|
116
125
|
def trace_node(node, &)
|
|
117
|
-
recorder =
|
|
126
|
+
recorder = recording
|
|
118
127
|
return yield unless recorder
|
|
119
128
|
|
|
120
129
|
recorder.node(node, &)
|
|
121
130
|
end
|
|
122
131
|
|
|
132
|
+
# The recorder installed on this thread, touched because the caller is about to record into it, or nil.
|
|
133
|
+
def recording
|
|
134
|
+
state = TypingState.current
|
|
135
|
+
recorder = state[RECORDER]
|
|
136
|
+
state.touch(RECORDER) if recorder
|
|
137
|
+
recorder
|
|
138
|
+
end
|
|
139
|
+
|
|
123
140
|
# `Scope#with_local` — the moment a local enters the scope.
|
|
124
141
|
def bind(name, type)
|
|
125
|
-
|
|
142
|
+
recording&.emit(:bind, data: { name: name.to_s, type: describe(type) })
|
|
126
143
|
end
|
|
127
144
|
|
|
128
145
|
# `Type::Combinator.union` — the moment branch types merge (including degenerate collapses like `1 | 1 → 1`).
|
|
129
146
|
def union(members, result)
|
|
130
|
-
|
|
147
|
+
recording&.emit(
|
|
131
148
|
:union,
|
|
132
149
|
data: { members: members.map { |m| describe(m) }.freeze, type: describe(result) }
|
|
133
150
|
)
|
|
@@ -136,7 +153,7 @@ module Rigor
|
|
|
136
153
|
# `MethodDispatcher.dispatch` — resolution or the fail-soft `nil` ("no rule matched"; the caller will widen
|
|
137
154
|
# to `Dynamic[Top]`).
|
|
138
155
|
def dispatch(receiver:, method_name:, args:, result:, location: nil)
|
|
139
|
-
recorder =
|
|
156
|
+
recorder = recording
|
|
140
157
|
return unless recorder
|
|
141
158
|
|
|
142
159
|
recorder.emit(
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "tmpdir"
|
|
4
4
|
|
|
5
5
|
require_relative "../runtime/jit"
|
|
6
|
+
require_relative "../type_of_memo"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Inference
|
|
@@ -95,6 +96,7 @@ module Rigor
|
|
|
95
96
|
# what is left of the parent's window rather than a fresh one; {Runtime::Jit.rearm_after_fork}
|
|
96
97
|
# carries the mechanism.
|
|
97
98
|
Runtime::Jit.rearm_after_fork
|
|
99
|
+
TypeOfMemo.forked
|
|
98
100
|
# Published before the block runs, so anything it lazily creates for this process alone lands in the
|
|
99
101
|
# parent's tmpdir and dies with it. See {.child_scratch_dir}.
|
|
100
102
|
@child_scratch_dir = File.dirname(out_path)
|
|
@@ -105,6 +107,7 @@ module Rigor
|
|
|
105
107
|
ensure
|
|
106
108
|
@child_scratch_dir = nil
|
|
107
109
|
end
|
|
110
|
+
TypeOfMemo.write_report
|
|
108
111
|
exit!(status)
|
|
109
112
|
end
|
|
110
113
|
|
|
@@ -23,7 +23,8 @@ module Rigor
|
|
|
23
23
|
# what it reads. Unlike a root block it shares that slot, so a match in it still rebinds the definer's `$~`.
|
|
24
24
|
#
|
|
25
25
|
# `Enumerator.new` is neither: its block runs on an internal fiber whose root is not the block, so it reads and
|
|
26
|
-
# rebinds the creator's slot
|
|
26
|
+
# rebinds the creator's slot. It reads that slot when the enumerator is read, though, not where the block is
|
|
27
|
+
# written ({.deferred_body?}, issue #3195).
|
|
27
28
|
#
|
|
28
29
|
# The receiver of a root call must be the literal constant, and must resolve to the core class where a scope is
|
|
29
30
|
# at hand, so a project's own `Thread` (`module Pool; class Thread; …`) keeps the ordinary block rules.
|
|
@@ -115,6 +116,56 @@ module Rigor
|
|
|
115
116
|
scope.forget_error_info.forget_last_status
|
|
116
117
|
end
|
|
117
118
|
|
|
119
|
+
# The core constructors that keep their block and run it on a schedule of their own: an enumerator's generator
|
|
120
|
+
# runs when the enumerator is first read, and a hash's default proc on a missing key.
|
|
121
|
+
DEFERRED_CONSTRUCTORS = Set[:Enumerator, :Hash].freeze
|
|
122
|
+
private_constant :DEFERRED_CONSTRUCTORS
|
|
123
|
+
|
|
124
|
+
# Issue #1371, #3195 — true when `block_node` is a body that runs later than where it is written, on the slot of
|
|
125
|
+
# the frame that wrote it: a proc's ({.proc_body?}), or the block of `Enumerator.new` or `Hash.new` on the core
|
|
126
|
+
# class ({.deferred_constructor?}). Such a body runs whenever the proc is called, the enumerator is read or a
|
|
127
|
+
# key is missing, so the frame-local specials narrowed where it is written prove nothing about the slot it
|
|
128
|
+
# reads then. It enters with `$_` unbound ({LastLine.block_entry}), and with the match globals as
|
|
129
|
+
# {MatchRebinding.block_entry} gives each kind. It still shares the slot, so a match or a reader it runs
|
|
130
|
+
# rebinds the frame's as any block's does. The other calls that keep their block ({StoredBlockCall}) are a
|
|
131
|
+
# definer's or a root's ({.entry}).
|
|
132
|
+
def deferred_body?(block_node, call_node, scope)
|
|
133
|
+
proc_body?(block_node, call_node, scope) || deferred_constructor?(block_node, call_node, scope)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Issue #1371 — true when `block_node` is the body of a proc made where it is written: a lambda literal, or the
|
|
137
|
+
# block of Kernel's `lambda` / `proc` or of `Proc.new` on the core class. It enters with the match globals
|
|
138
|
+
# unbound; a project's own `Proc` keeps the ordinary entry.
|
|
139
|
+
# Only a lambda literal the statement evaluator enters gets this entry: one written as an operand is typed as a
|
|
140
|
+
# value, and the rule walker reads its body in the enclosing scope.
|
|
141
|
+
def proc_body?(block_node, call_node, scope)
|
|
142
|
+
return true if block_node.is_a?(Prism::LambdaNode)
|
|
143
|
+
return false unless call_node.is_a?(Prism::CallNode) && call_node.block.equal?(block_node)
|
|
144
|
+
|
|
145
|
+
receiver = call_node.receiver
|
|
146
|
+
case call_node.name
|
|
147
|
+
when :lambda, :proc then StoredBlockCall.kernel_spelled?(receiver)
|
|
148
|
+
when :new then StoredBlockCall.root_constant_name(receiver) == :Proc && core_class?(receiver, :Proc, scope)
|
|
149
|
+
else false
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Issue #3195 — true when `block_node` is the block of `Enumerator.new` or `Hash.new` on the literal constant
|
|
154
|
+
# naming the core class. The block often runs right away, in the statement that makes it
|
|
155
|
+
# (`Enumerator.new { … }.to_a`, `Hash.new { … }[key]`), and reads the narrowing then; it may as well run after a
|
|
156
|
+
# later match. The analyzer does not follow which, so the body enters with a narrowed match global untyped, as
|
|
157
|
+
# a definer body does ({.entry}): neither narrowed nor flagged. A project's own `Enumerator` or `Hash` keeps
|
|
158
|
+
# the ordinary entry, and so does every other spelling of the core class: a subclass (`Class.new(Hash)`), an
|
|
159
|
+
# alias (`E = Enumerator`), `Object::Enumerator`, or a receiver that is not a constant (`klass.new { … }`).
|
|
160
|
+
def deferred_constructor?(block_node, call_node, scope)
|
|
161
|
+
return false unless call_node.is_a?(Prism::CallNode) && call_node.name == :new &&
|
|
162
|
+
call_node.block.equal?(block_node)
|
|
163
|
+
|
|
164
|
+
receiver = call_node.receiver
|
|
165
|
+
constant = StoredBlockCall.root_constant_name(receiver)
|
|
166
|
+
DEFERRED_CONSTRUCTORS.include?(constant) && core_class?(receiver, constant, scope)
|
|
167
|
+
end
|
|
168
|
+
|
|
118
169
|
def core_class?(receiver, constant, scope)
|
|
119
170
|
type = scope.type_of(receiver)
|
|
120
171
|
type.is_a?(Type::Singleton) && type.class_name == constant.name
|
|
@@ -5,6 +5,7 @@ require "prism"
|
|
|
5
5
|
require_relative "../source/constant_path"
|
|
6
6
|
require_relative "../source/node_children"
|
|
7
7
|
require_relative "ivar_writer_census"
|
|
8
|
+
require_relative "stored_block_call"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
10
11
|
module Inference
|
|
@@ -28,7 +29,15 @@ module Rigor
|
|
|
28
29
|
# - `[:main_mixin, name]` — a module a top-level `include`, `prepend` or `extend` mixes into `Object` (or
|
|
29
30
|
# `main`), as written. A module RBS does not know may carry any method, so it reaches every class.
|
|
30
31
|
# - the instance-variable writer entries of {IvarWriterCensus} (issue #1154): `[:ivar_set, name]`,
|
|
31
|
-
# `[:ivar_set_class, class_name]
|
|
32
|
+
# `[:ivar_set_class, class_name]`, `[:own_write, class_name, name, path]`, issue #2079's
|
|
33
|
+
# `[:own_write_type, class_name, name, value, path]` and issue #2983's `[:binds_class, class_key, constant]`.
|
|
34
|
+
# - `[:global_writer, name, method]` — issue #2314: a write of the global `name` inside a method body, keyed by
|
|
35
|
+
# the `def`'s name (or the literal name a `define_method` block defines), or by nil inside a lambda or a block
|
|
36
|
+
# its call keeps to run later ({StoredBlockCall}). A write in a block its call runs at once belongs to the
|
|
37
|
+
# enclosing body. A write at the top level or in a class body records nothing, and neither does a write of
|
|
38
|
+
# the frame-local `$_` or `$~`. {GlobalWriterReach} reads these.
|
|
39
|
+
# - `[:method_alias, new, old]` — issue #2314: `alias new old`, `alias_method :new, :old` or `def_delegator
|
|
40
|
+
# :@o, :old, :new`, whose new name runs a method named old. {GlobalWriterReach} follows it.
|
|
32
41
|
#
|
|
33
42
|
# The census is a `Set` of those frozen entries, merged by union across files, the project pre-pass and the
|
|
34
43
|
# `pre_eval:` files. It is recorded inside descents that already visit every node ({Collector#visit}), never
|
|
@@ -78,7 +87,7 @@ module Rigor
|
|
|
78
87
|
end
|
|
79
88
|
|
|
80
89
|
# #1154 — the {IvarWriterCensus} entries of `census`, for the incremental diff and its readers.
|
|
81
|
-
IVAR_KINDS = %i[ivar_set ivar_set_class own_write].to_set.freeze
|
|
90
|
+
IVAR_KINDS = %i[ivar_set ivar_set_class own_write own_write_type binds_class].to_set.freeze
|
|
82
91
|
|
|
83
92
|
def ivar_set_entries(census) = census.select { |entry| IVAR_KINDS.include?(entry.first) }.to_set
|
|
84
93
|
|
|
@@ -87,6 +96,8 @@ module Rigor
|
|
|
87
96
|
case entry.first
|
|
88
97
|
when :ivar_set then "ivar_set:#{entry[1]}"
|
|
89
98
|
when :ivar_set_class then "ivar_set_class:#{entry[1]}"
|
|
99
|
+
when :own_write_type then "own_write_type:#{entry[1]}##{entry[2]}"
|
|
100
|
+
when :binds_class then "binds_class:#{entry[1].split(CensusForeignBlock::CANDIDATE_SEPARATOR).last.split('::').last}"
|
|
90
101
|
else "own_write:#{entry[1]}##{entry[2]}"
|
|
91
102
|
end
|
|
92
103
|
end
|
|
@@ -140,14 +151,17 @@ module Rigor
|
|
|
140
151
|
@refine_ranges = []
|
|
141
152
|
@ivar_writers = IvarWriterCensus.new(@census)
|
|
142
153
|
@in_effect = in_effect
|
|
154
|
+
@bodies = []
|
|
143
155
|
end
|
|
144
156
|
|
|
145
157
|
# A node the collector cannot read must not fail the analysis of its file, nor of every file the pre-pass
|
|
146
158
|
# feeds: the census then counts it as a definition of every name, which only declines a report.
|
|
147
159
|
# `owner` is the qualified name of the class whose body the node sits in, for {IvarWriterCensus} (nil where
|
|
148
|
-
# the host walk does not track it, or no class owns an instance there)
|
|
149
|
-
|
|
150
|
-
|
|
160
|
+
# the host walk does not track it, or no class owns an instance there); `receiver_owner` the class an
|
|
161
|
+
# explicit-receiver writer call names (issue #2488).
|
|
162
|
+
def visit(node, top_level:, owner: nil, receiver_owner: nil)
|
|
163
|
+
@ivar_writers.visit(node, owner, receiver_owner)
|
|
164
|
+
visit_global_writer(node)
|
|
151
165
|
case node
|
|
152
166
|
when Prism::AliasGlobalVariableNode then visit_global_alias(node)
|
|
153
167
|
when Prism::DefNode then record(node, node.name)
|
|
@@ -160,6 +174,68 @@ module Rigor
|
|
|
160
174
|
|
|
161
175
|
private
|
|
162
176
|
|
|
177
|
+
# Issue #2314 — tracks the method bodies, lambdas and stored blocks of a pre-order descent as flat
|
|
178
|
+
# `start, end, label` triples (`@bodies`, innermost last), and records a global write inside one
|
|
179
|
+
# ({GlobalWriterReach}). A body ending before the write starts is closed. Only those nodes read their location.
|
|
180
|
+
def visit_global_writer(node)
|
|
181
|
+
case node
|
|
182
|
+
when Prism::DefNode then open_body(node, node.name)
|
|
183
|
+
when Prism::LambdaNode then open_body(node, nil)
|
|
184
|
+
when Prism::AliasMethodNode then record_method_alias(node.new_name, node.old_name)
|
|
185
|
+
when Prism::CallNode
|
|
186
|
+
open_stored_block(node)
|
|
187
|
+
visit_alias_call(node)
|
|
188
|
+
when Prism::GlobalVariableWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode,
|
|
189
|
+
Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableTargetNode
|
|
190
|
+
record_global_writer(node)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# `$_` and `$~` are the frame's own: a method's write to one never reaches its caller.
|
|
195
|
+
FRAME_LOCAL_GLOBALS = %i[$_ $~].freeze
|
|
196
|
+
private_constant :FRAME_LOCAL_GLOBALS
|
|
197
|
+
|
|
198
|
+
def record_global_writer(node)
|
|
199
|
+
return if FRAME_LOCAL_GLOBALS.include?(node.name)
|
|
200
|
+
|
|
201
|
+
start = node.location.start_offset
|
|
202
|
+
bodies = @bodies
|
|
203
|
+
bodies.pop(3) while !bodies.empty? && bodies[-2] <= start
|
|
204
|
+
index = bodies.size - 3
|
|
205
|
+
index -= 3 while index >= 0 && !(bodies[index] <= start && start < bodies[index + 1])
|
|
206
|
+
@census << [:global_writer, node.name, bodies[index + 2]].freeze if index >= 0
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def open_body(node, label)
|
|
210
|
+
location = node.location
|
|
211
|
+
@bodies.push(location.start_offset, location.end_offset, label)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# `alias_method :new, :old`, and `def_delegator :@o, :old, :new` (and its instance and single spellings), whose
|
|
215
|
+
# new name runs a method of the old name.
|
|
216
|
+
def visit_alias_call(node)
|
|
217
|
+
arguments = node.arguments&.arguments
|
|
218
|
+
case node.name
|
|
219
|
+
when :alias_method then record_method_alias(arguments[0], arguments[1]) if arguments&.size == 2
|
|
220
|
+
when :def_delegator, :def_instance_delegator, :def_single_delegator
|
|
221
|
+
record_method_alias(arguments[2], arguments[1]) if arguments&.size == 3
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def record_method_alias(new_node, old_node)
|
|
226
|
+
new_name = literal_name(new_node)
|
|
227
|
+
old_name = literal_name(old_node)
|
|
228
|
+
@census << [:method_alias, new_name, old_name].freeze if new_name && old_name && new_name != old_name
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def open_stored_block(node)
|
|
232
|
+
block = node.block
|
|
233
|
+
return unless block.is_a?(Prism::BlockNode) && StoredBlockCall.stores_block?(node)
|
|
234
|
+
|
|
235
|
+
definer = %i[define_method define_singleton_method].include?(node.name)
|
|
236
|
+
open_body(block, definer ? literal_name(node.arguments&.arguments&.first) : nil)
|
|
237
|
+
end
|
|
238
|
+
|
|
163
239
|
def visit_global_alias(node)
|
|
164
240
|
[node.new_name, node.old_name].each do |side|
|
|
165
241
|
@census << GlobalWriteCensus.alias_entry(side.name) if side.is_a?(Prism::GlobalVariableReadNode)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../analysis/dependency_recorder"
|
|
6
|
+
require_relative "../source/node_children"
|
|
7
|
+
require_relative "../type"
|
|
8
|
+
require_relative "guard_rebinding"
|
|
9
|
+
require_relative "project_method_ownership"
|
|
10
|
+
|
|
11
|
+
module Rigor
|
|
12
|
+
module Inference
|
|
13
|
+
# Issue #2314 — whether running a node may reach a project method that writes a given global. Both places that
|
|
14
|
+
# end a global's binding at a call ask it: the statement walk past a statement call
|
|
15
|
+
# (`StatementEvaluator#forget_reached_globals`), and a `case` whose global subject a `when` test may rebind
|
|
16
|
+
# (`StatementEvaluator#when_test_rebinds_subject?`, issue #2211), so the two read a global alike.
|
|
17
|
+
#
|
|
18
|
+
# The writers come from {GlobalWriteCensus}: a `[:global_writer, name, method]` entry for each write of `name`
|
|
19
|
+
# inside a method body (`method` is the `def`'s name, or the literal name a `define_method` block defines), and
|
|
20
|
+
# `[:global_writer, name, nil]` for one inside a lambda or a block a call keeps to run later
|
|
21
|
+
# ({StoredBlockCall}), which any call may run. A write at the top level or in a class body runs where it is
|
|
22
|
+
# written, so no call reaches it.
|
|
23
|
+
#
|
|
24
|
+
# {.may_write?} answers true when the global has a writer and:
|
|
25
|
+
#
|
|
26
|
+
# - the node may run code other than Ruby core and the standard library ({GuardRebinding.may_rebind?}, the
|
|
27
|
+
# question #2260 asked of a `when` test), and
|
|
28
|
+
# - some writer is anonymous, or the node's own call has a receiver no target resolves (a `Dynamic` value), or a
|
|
29
|
+
# search by method name reaches a writer ({Search}).
|
|
30
|
+
#
|
|
31
|
+
# The search reads method names, not receivers: from the node it follows every call, by name, into every project
|
|
32
|
+
# `def` of that name on any class, transitively, and reaches a writer when it meets a call named after one. A
|
|
33
|
+
# call naming its method by a computed value (`send(name)`, `eval(code)`) reaches. A name no project `def` defines
|
|
34
|
+
# follows `method_missing`, `new` follows `initialize`, and an alias name (`alias`, `alias_method`, a
|
|
35
|
+
# `def_delegator` alias) follows the name it stands for. A nested `def` is not read, since defining a method runs
|
|
36
|
+
# nothing. Past {Search::DEF_BUDGET} bodies the search answers that it reaches. A `define_method` method that calls
|
|
37
|
+
# a writer is not followed (only its own writes count), and a method core code calls back without the program
|
|
38
|
+
# spelling it (`puts obj` runs `obj.to_s`) is read as the core method alone, as {GuardRebinding} reads it.
|
|
39
|
+
module GlobalWriterReach
|
|
40
|
+
EMPTY = Set.new.freeze
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
# The globals `scope` binds that some project method may write, other than one already `Dynamic[top]`. Each
|
|
45
|
+
# bound global's census key is recorded whatever the answer, so a file that starts writing one re-checks.
|
|
46
|
+
def candidates(scope)
|
|
47
|
+
table = writer_table(scope)
|
|
48
|
+
globals = scope.globals
|
|
49
|
+
globals.each_key { |name| record_writers(name) } if Analysis::DependencyRecorder.active?
|
|
50
|
+
return EMPTY if table.empty? || globals.empty?
|
|
51
|
+
|
|
52
|
+
table.each_key.select do |name|
|
|
53
|
+
type = globals[name]
|
|
54
|
+
!type.nil? && !type.is_a?(Type::Dynamic)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# True when running `node`, typed in `scope`, may reach a project method that writes `name` (see the module
|
|
59
|
+
# comment).
|
|
60
|
+
def may_write?(node, name, scope)
|
|
61
|
+
writers = writers(scope, name)
|
|
62
|
+
return false if writers.empty?
|
|
63
|
+
return false unless GuardRebinding.may_rebind?(node, scope)
|
|
64
|
+
return true if writers.include?(nil) || unresolved_call?(node, scope)
|
|
65
|
+
|
|
66
|
+
Search.new(scope, writers).reaches?(node)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The method names that write `name` (nil for an anonymous writer). Records the census key, so a file that
|
|
70
|
+
# starts or stops writing the global re-checks the reader. A guard on the `case` site: every `when` test built so
|
|
71
|
+
# far also records the key through a statement call's {.candidates} or a callee's method edge.
|
|
72
|
+
def writers(scope, name)
|
|
73
|
+
record_writers(name) if Analysis::DependencyRecorder.active?
|
|
74
|
+
writer_table(scope)[name] || EMPTY
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def record_writers(name)
|
|
78
|
+
Analysis::DependencyRecorder.read_name(:global_census, "global_writer:#{name}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# `{ global => Set[method name or nil] }` over the project's census and the `pre_eval:` files' own.
|
|
82
|
+
def writer_table(scope) = census_tables(scope).first
|
|
83
|
+
|
|
84
|
+
# `{ alias name => Set[the method names it stands for] }`: an `alias`, an `alias_method` or a `def_delegator`
|
|
85
|
+
# with a literal alias name, from the same census.
|
|
86
|
+
def alias_table(scope) = census_tables(scope).last
|
|
87
|
+
|
|
88
|
+
# The two tables above, kept for the census objects they were built from.
|
|
89
|
+
def census_tables(scope)
|
|
90
|
+
census = scope.discovered_global_write_census
|
|
91
|
+
pre_eval = scope.environment&.project_patched_methods&.write_census
|
|
92
|
+
cached = @census_tables
|
|
93
|
+
return cached[2] if cached && cached[0].equal?(census) && cached[1].equal?(pre_eval)
|
|
94
|
+
|
|
95
|
+
tables = build_tables([census, pre_eval])
|
|
96
|
+
@census_tables = [census, pre_eval, tables.freeze]
|
|
97
|
+
tables
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
TABLE_KINDS = { global_writer: 0, method_alias: 1 }.freeze
|
|
101
|
+
private_constant :TABLE_KINDS
|
|
102
|
+
|
|
103
|
+
def build_tables(censuses)
|
|
104
|
+
tables = [{}, {}]
|
|
105
|
+
censuses.each do |entries|
|
|
106
|
+
entries&.each do |kind, key, value|
|
|
107
|
+
index = TABLE_KINDS[kind]
|
|
108
|
+
(tables[index][key] ||= Set.new) << value if index
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
tables.each { |table| table.each_value(&:freeze).freeze }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# True when `node` is a call whose receiver no target resolves: the method it runs may be any.
|
|
115
|
+
def unresolved_call?(node, scope)
|
|
116
|
+
return false unless node.is_a?(Prism::CallNode)
|
|
117
|
+
|
|
118
|
+
receiver = node.receiver
|
|
119
|
+
return false if receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
120
|
+
|
|
121
|
+
targets = ProjectMethodOwnership.targets(GuardRebinding::ScanScope.dispatch_type(receiver, scope))
|
|
122
|
+
targets.nil? || targets.empty?
|
|
123
|
+
rescue StandardError
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# `{ method name => [[class name, kind], …] }` over the project's `def` tables, kept for the tables it was
|
|
128
|
+
# built from.
|
|
129
|
+
def def_index(scope)
|
|
130
|
+
instance = scope.discovered_def_nodes
|
|
131
|
+
singleton = scope.discovered_singleton_def_nodes
|
|
132
|
+
cached = @def_index
|
|
133
|
+
return cached[2] if cached && cached[0].equal?(instance) && cached[1].equal?(singleton)
|
|
134
|
+
|
|
135
|
+
index = {}
|
|
136
|
+
{ instance: instance, singleton: singleton }.each do |kind, table|
|
|
137
|
+
table.each do |class_name, methods|
|
|
138
|
+
methods.each_key { |method_name| (index[method_name] ||= []) << [class_name, kind] }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
@def_index = [instance, singleton, index.freeze]
|
|
142
|
+
index
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# A breadth-first search by method name from a node to a writer's name.
|
|
146
|
+
class Search
|
|
147
|
+
# The most `def` bodies one search reads before it answers that it reaches.
|
|
148
|
+
DEF_BUDGET = 64
|
|
149
|
+
SEND_CALLS = %i[send __send__ public_send method public_method instance_method].to_set.freeze
|
|
150
|
+
CODE_CALLS = %i[eval instance_eval instance_exec class_eval class_exec module_eval module_exec].to_set.freeze
|
|
151
|
+
|
|
152
|
+
class Reached < StandardError
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def initialize(scope, writers)
|
|
156
|
+
@scope = scope
|
|
157
|
+
@writers = writers
|
|
158
|
+
@aliases = GlobalWriterReach.alias_table(scope)
|
|
159
|
+
@recording = Analysis::DependencyRecorder.active?
|
|
160
|
+
@seen = Set.new
|
|
161
|
+
@queue = []
|
|
162
|
+
@read = 0
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def reaches?(node)
|
|
166
|
+
scan(node)
|
|
167
|
+
until @queue.empty?
|
|
168
|
+
name = @queue.shift
|
|
169
|
+
definitions = definitions_of(name)
|
|
170
|
+
enqueue(:method_missing) if definitions.empty?
|
|
171
|
+
definitions.each { |definition| read_def(definition) }
|
|
172
|
+
end
|
|
173
|
+
false
|
|
174
|
+
rescue Reached
|
|
175
|
+
true
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def read_def(definition)
|
|
181
|
+
@read += 1
|
|
182
|
+
reached! if @read > DEF_BUDGET
|
|
183
|
+
scan(definition.parameters)
|
|
184
|
+
scan(definition.body)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def definitions_of(name)
|
|
188
|
+
Analysis::DependencyRecorder.read_name(:defines, name) if Analysis::DependencyRecorder.active?
|
|
189
|
+
targets = GlobalWriterReach.def_index(@scope)[name] || []
|
|
190
|
+
targets.filter_map do |class_name, kind|
|
|
191
|
+
kind == :singleton ? @scope.singleton_def_for(class_name, name) : @scope.user_def_for(class_name, name)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def scan(node)
|
|
196
|
+
return unless node.is_a?(Prism::Node)
|
|
197
|
+
# A nested `def` defines a method; it runs nothing here, and its name is followed where it is called.
|
|
198
|
+
return if node.is_a?(Prism::DefNode)
|
|
199
|
+
|
|
200
|
+
case node
|
|
201
|
+
when Prism::CallNode then scan_call(node)
|
|
202
|
+
when Prism::BlockArgumentNode then scan_block_argument(node)
|
|
203
|
+
when Prism::ForNode then enqueue(:each)
|
|
204
|
+
end
|
|
205
|
+
implicit_names(node).each { |name| enqueue(name) }
|
|
206
|
+
node.rigor_each_child { |child| scan(child) }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def scan_call(node)
|
|
210
|
+
enqueue(node.name)
|
|
211
|
+
# `new` runs the class's `initialize`.
|
|
212
|
+
enqueue(:initialize) if node.name == :new
|
|
213
|
+
arguments = node.arguments&.arguments || []
|
|
214
|
+
if SEND_CALLS.include?(node.name)
|
|
215
|
+
name = literal_name(arguments.first)
|
|
216
|
+
name ? enqueue(name) : reached!
|
|
217
|
+
elsif CODE_CALLS.include?(node.name) && !(arguments.empty? && node.block.is_a?(Prism::BlockNode))
|
|
218
|
+
reached!
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def scan_block_argument(node)
|
|
223
|
+
name = literal_name(node.expression)
|
|
224
|
+
enqueue(name) if name
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# The methods an operator or compound write calls without spelling them.
|
|
228
|
+
def implicit_names(node)
|
|
229
|
+
names = []
|
|
230
|
+
names << node.binary_operator if node.respond_to?(:binary_operator)
|
|
231
|
+
names.push(node.read_name, node.write_name) if node.respond_to?(:read_name) && node.respond_to?(:write_name)
|
|
232
|
+
names
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def enqueue(name)
|
|
236
|
+
return if name.nil? || @seen.include?(name)
|
|
237
|
+
|
|
238
|
+
reached! if @writers.include?(name)
|
|
239
|
+
@seen << name
|
|
240
|
+
@queue << name
|
|
241
|
+
# An alias runs the method it names.
|
|
242
|
+
Analysis::DependencyRecorder.read_name(:global_census, "method_alias:#{name}") if @recording
|
|
243
|
+
@aliases[name]&.each { |original| enqueue(original) }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def literal_name(node)
|
|
247
|
+
return unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
|
|
248
|
+
|
|
249
|
+
text = node.unescaped
|
|
250
|
+
text.to_sym if text.valid_encoding?
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def reached!
|
|
254
|
+
raise Reached
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
private_constant :Search
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
@@ -41,8 +41,7 @@ module Rigor
|
|
|
41
41
|
|
|
42
42
|
# Reference to a formal parameter the enclosing `Definition#params` declared. The reducer
|
|
43
43
|
# substitutes this node with the matching positional arg from the `App` being reduced; an
|
|
44
|
-
# unknown name raises during reduction
|
|
45
|
-
# earlier).
|
|
44
|
+
# unknown name raises during reduction.
|
|
46
45
|
Param = Data.define(:name) do
|
|
47
46
|
def initialize(name:)
|
|
48
47
|
raise ArgumentError, "name must be a Symbol, got #{name.class}" unless name.is_a?(Symbol)
|
|
@@ -43,7 +43,7 @@ module Rigor
|
|
|
43
43
|
# `Rigor::Type::*` carrier: `nil` / `true` / `false` produce `Constant` carriers; `bool`
|
|
44
44
|
# produces the `Constant<true> | Constant<false>` union; `untyped` produces
|
|
45
45
|
# `Combinator.untyped` (i.e. `Dynamic[Top]`). Nominal class names produce raw `Type::Nominal`
|
|
46
|
-
# carriers (no `name_scope` resolution
|
|
46
|
+
# carriers (no `name_scope` resolution; the reducer trusts the name verbatim).
|
|
47
47
|
module HktBodyParser
|
|
48
48
|
class ParseError < StandardError; end
|
|
49
49
|
|
|
@@ -151,10 +151,8 @@ module Rigor
|
|
|
151
151
|
# test := type_expr ("<:" | "==") type_expr
|
|
152
152
|
#
|
|
153
153
|
# Parens delimit a conditional unambiguously — bare `(type_expr)` grouping is not
|
|
154
|
-
# supported
|
|
155
|
-
#
|
|
156
|
-
# membership tests are programmatically supported via `HktBody::TestMembership` but the
|
|
157
|
-
# parser does not yet recognise the `in` keyword (no concrete demand yet).
|
|
154
|
+
# supported (no use case yet). Branches can be unions; test sides are single arms (wrap
|
|
155
|
+
# in `App[my_union, ...]` if you need a union there).
|
|
158
156
|
def parse_conditional
|
|
159
157
|
expect!(:lparen)
|
|
160
158
|
test = parse_test
|
|
@@ -152,8 +152,8 @@ module Rigor
|
|
|
152
152
|
def subtype_verdict(left, right)
|
|
153
153
|
return :yes if left == right
|
|
154
154
|
|
|
155
|
-
#
|
|
156
|
-
#
|
|
155
|
+
# Two Nominals of different class names are statically disjoint (`:no`); any other shape
|
|
156
|
+
# pair is `:maybe`.
|
|
157
157
|
return :no if disjoint_nominals?(left, right)
|
|
158
158
|
return :no if disjoint_constants?(left, right)
|
|
159
159
|
|
|
@@ -9,11 +9,9 @@ module Rigor
|
|
|
9
9
|
# annotations in shipped `.rbs` files.
|
|
10
10
|
#
|
|
11
11
|
# The registry stores registration metadata (arity, variance, bound) and the definition body
|
|
12
|
-
# as both a raw String and an evaluable `HktBody` node tree. `HktReducer`
|
|
13
|
-
# `
|
|
14
|
-
#
|
|
15
|
-
# carries its `bound` directly; the registry exists at this slice solely so the parser
|
|
16
|
-
# round-trip and downstream slices have a stable target API.
|
|
12
|
+
# as both a raw String and an evaluable `HktBody` node tree. `HktReducer` and `HktBodyParser`
|
|
13
|
+
# reduce `Type::App` instances against the definition. The carrier never needs to read from the
|
|
14
|
+
# registry because `Type::App` carries its `bound` directly.
|
|
17
15
|
#
|
|
18
16
|
# The registry is immutable after construction. Callers that need to extend it (e.g. plugin
|
|
19
17
|
# registrations layered on top of stdlib registrations) MUST build a new registry via `merge`
|