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
|
@@ -80,6 +80,20 @@ module Rigor
|
|
|
80
80
|
type.is_a?(Type::Constant) && type.value.is_a?(String)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
# A predicate refinement over String (`decimal-int-string`, `lowercase-string`) under a String mutator → its
|
|
84
|
+
# `String` base. Any in-place mutation can falsify the predicate (`s.clear` leaves a `decimal-int-string`
|
|
85
|
+
# empty, `s << "A"` a `lowercase-string` mixed-case), so none keeps it. `nil` for every other input.
|
|
86
|
+
def widen_refined(refined, method_name)
|
|
87
|
+
return nil unless refined?(refined)
|
|
88
|
+
return nil unless MUTATORS.include?(method_name)
|
|
89
|
+
|
|
90
|
+
refined.base
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def refined?(type)
|
|
94
|
+
type.is_a?(Type::Refined) && type.base.is_a?(Type::Nominal) && type.base.class_name == "String"
|
|
95
|
+
end
|
|
96
|
+
|
|
83
97
|
# Whether `method_name`, called with `arg_types` (the call's positional argument types, possibly none), can leave
|
|
84
98
|
# a non-empty receiver empty, and so retract a `non-empty-string` refinement. Every mutator outside
|
|
85
99
|
# {EMPTY_PRESERVING} can, except a {TRANSLATORS} call whose replacement is provably non-empty.
|
|
@@ -56,8 +56,7 @@ module Rigor
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# @param root — the local-variable scope to scan.
|
|
59
|
-
# @param layout_lookup — a
|
|
60
|
-
# struct member list.
|
|
59
|
+
# @param layout_lookup — a resolver mapping a constant receiver name to its struct member list.
|
|
61
60
|
# @return the fold-safe local names.
|
|
62
61
|
def fold_safe_locals(root, layout_lookup)
|
|
63
62
|
return EMPTY if root.nil?
|
|
@@ -283,8 +282,7 @@ module Rigor
|
|
|
283
282
|
# `[1].each { s.x = v }; s.x` would read the construction value — verified unsound on #525.
|
|
284
283
|
#
|
|
285
284
|
# `for` shares the enclosing locals rather than opening a scope, so the block rationale does not apply
|
|
286
|
-
# to it; it is a boundary for the same reason `while` is (below)
|
|
287
|
-
# wrong reason for it.
|
|
285
|
+
# to it; it is a boundary for the same reason `while` is (below).
|
|
288
286
|
#
|
|
289
287
|
# `while` / `until` are boundaries because a single static pass cannot model a member setter across
|
|
290
288
|
# ITERATIONS. Issue #597 tried to lift this: `eval_loop` does join the body's exit scope with the
|
|
@@ -327,8 +325,8 @@ module Rigor
|
|
|
327
325
|
return nil unless meta_constant?(call_node.receiver, :Struct)
|
|
328
326
|
|
|
329
327
|
args = call_node.arguments&.arguments || []
|
|
330
|
-
# `[0..-2]` cannot return nil for a start of 0, but `Array#[]
|
|
331
|
-
# the `|| []` keeps the read well-typed on that worst case.
|
|
328
|
+
# `[0..-2]` cannot return nil for a start of 0, but the RBS signature of `Array#[]` with a Range says it
|
|
329
|
+
# can; the `|| []` keeps the read well-typed on that worst case.
|
|
332
330
|
positional = args.last.is_a?(Prism::KeywordHashNode) ? (args[0..-2] || []) : args
|
|
333
331
|
positional = positional[1..] if positional.first.is_a?(Prism::StringNode)
|
|
334
332
|
return nil if positional.nil? || positional.empty?
|
|
@@ -64,11 +64,10 @@ module Rigor
|
|
|
64
64
|
registries = collect_trait_registries(plugin_registry)
|
|
65
65
|
nested_templates = collect_nested_class_templates(plugin_registry)
|
|
66
66
|
return SyntheticMethodIndex::EMPTY if templates.empty? && registries.empty? && nested_templates.empty?
|
|
67
|
-
# Tier B alone cannot emit without an environment: every trait entry
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# no environment would otherwise pay a whole-project parse to build a provably empty index. It mirrors
|
|
67
|
+
# Tier B alone cannot emit without an environment: every trait entry funnels through
|
|
68
|
+
# `module_instance_method_names`, which answers `[]` for a nil environment. The production pre-pass always
|
|
69
|
+
# passes one (#476), so this gate guards the direct callers that pass none (a spec, a probe): a
|
|
70
|
+
# registry-only scan would otherwise pay a whole-project parse to build a provably empty index. It mirrors
|
|
72
71
|
# that nil guard and goes with it.
|
|
73
72
|
return SyntheticMethodIndex::EMPTY if environment.nil? && templates.empty? && nested_templates.empty?
|
|
74
73
|
|
|
@@ -324,21 +323,29 @@ module Rigor
|
|
|
324
323
|
# - `class_methods do ... end` blocks are NOT yet handled —
|
|
325
324
|
# singleton-level emission is out of scope per the slice-3
|
|
326
325
|
# floor framing.
|
|
326
|
+
#
|
|
327
|
+
# Issue #3005 — the `included do` blocks of every body of a module some body extends with Concern are read: a
|
|
328
|
+
# reopening that does not repeat the `extend` runs its block on a module that is already a concern (loaded
|
|
329
|
+
# first, its `included do` raises, so no running program has the includer without the calls). A module no
|
|
330
|
+
# body extends with Concern contributes nothing.
|
|
327
331
|
CONCERN_NAME = "ActiveSupport::Concern"
|
|
328
332
|
|
|
329
333
|
def build_concern_index(asts)
|
|
330
|
-
|
|
334
|
+
bodies = Hash.new { |hash, name| hash[name] = [] }
|
|
331
335
|
asts.each_value do |ast|
|
|
332
336
|
next if ast.nil?
|
|
333
337
|
|
|
334
338
|
walk_module_decls(ast, []) do |module_name, body|
|
|
335
|
-
|
|
336
|
-
next unless concern_module_body?(body)
|
|
337
|
-
|
|
338
|
-
deferred_calls = collect_included_do_calls(body)
|
|
339
|
-
index[module_name] = deferred_calls.freeze if deferred_calls.any?
|
|
339
|
+
bodies[module_name] << body unless module_name.nil? || body.nil?
|
|
340
340
|
end
|
|
341
341
|
end
|
|
342
|
+
index = {}
|
|
343
|
+
bodies.each do |module_name, list|
|
|
344
|
+
next unless list.any? { |body| concern_module_body?(body) }
|
|
345
|
+
|
|
346
|
+
deferred_calls = list.flat_map { |body| collect_included_do_calls(body) }
|
|
347
|
+
index[module_name] = deferred_calls.freeze if deferred_calls.any?
|
|
348
|
+
end
|
|
342
349
|
index.freeze
|
|
343
350
|
end
|
|
344
351
|
|
|
@@ -360,18 +367,34 @@ module Rigor
|
|
|
360
367
|
end
|
|
361
368
|
|
|
362
369
|
# Recognises a module body that begins with (or contains at top level) an `extend ActiveSupport::Concern`
|
|
363
|
-
# statement.
|
|
370
|
+
# statement. Issue #3005 — `self.extend` and a receiverless or `self.` `send` / `public_send` /
|
|
371
|
+
# `__send__(:extend, …)` are the same call.
|
|
364
372
|
def concern_module_body?(body)
|
|
365
373
|
return false unless body.respond_to?(:body)
|
|
366
374
|
|
|
367
375
|
body.body.any? do |stmt|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
args = stmt.arguments&.arguments || []
|
|
371
|
-
args.any? { |arg| const_name_string(arg) == CONCERN_NAME }
|
|
376
|
+
args = concern_extend_arguments(stmt)
|
|
377
|
+
args&.any? { |arg| const_name_string(arg) == CONCERN_NAME }
|
|
372
378
|
end
|
|
373
379
|
end
|
|
374
380
|
|
|
381
|
+
CONCERN_SEND_CALLS = %i[send public_send __send__].freeze
|
|
382
|
+
private_constant :CONCERN_SEND_CALLS
|
|
383
|
+
|
|
384
|
+
# The modules an `extend` statement on the module's own `self` names, or nil for any other statement.
|
|
385
|
+
def concern_extend_arguments(stmt)
|
|
386
|
+
return nil unless stmt.is_a?(Prism::CallNode)
|
|
387
|
+
return nil unless stmt.receiver.nil? || stmt.receiver.is_a?(Prism::SelfNode)
|
|
388
|
+
|
|
389
|
+
args = stmt.arguments&.arguments || []
|
|
390
|
+
return args if stmt.name == :extend
|
|
391
|
+
return nil unless CONCERN_SEND_CALLS.include?(stmt.name)
|
|
392
|
+
|
|
393
|
+
name = args.first
|
|
394
|
+
sent = name.unescaped if name.is_a?(Prism::SymbolNode) || name.is_a?(Prism::StringNode)
|
|
395
|
+
sent == "extend" ? args.drop(1) : nil
|
|
396
|
+
end
|
|
397
|
+
|
|
375
398
|
def collect_included_do_calls(body)
|
|
376
399
|
body.body.flat_map do |stmt|
|
|
377
400
|
included_do = stmt.is_a?(Prism::CallNode) && stmt.receiver.nil? && stmt.name == :included
|
|
@@ -534,7 +557,7 @@ module Rigor
|
|
|
534
557
|
# (resolved through `registry.modules_by_symbol`; unknown
|
|
535
558
|
# symbols silently skipped per design decision (2)).
|
|
536
559
|
#
|
|
537
|
-
# Returns
|
|
560
|
+
# Returns the module names: the `always_included` ones first, then those for the symbol arguments in order.
|
|
538
561
|
def resolve_trait_modules(registry, call_node)
|
|
539
562
|
modules = registry.always_included.dup
|
|
540
563
|
positional_symbols(call_node, registry).each do |symbol|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../typing_state"
|
|
4
|
+
require_relative "../memo_audit"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
# Issue #2515 — whether the `<toplevel>` def table's answer for a name IS the method a receiverless call of that
|
|
9
|
+
# name runs, so the callee mutation floor may read the def as written. {.reliable?} is false (the floor reads every
|
|
10
|
+
# argument as written, `StatementEvaluator::FLOOR_EVERY`) when any of these holds for `name`:
|
|
11
|
+
#
|
|
12
|
+
# - {Scope#toplevel_def_ambiguity} names a reason (the slot is contested, several ownerless definition rows of the
|
|
13
|
+
# name exist, a `def self.name` defines it on `main`, an `alias` introduces it, an anonymous class body defines
|
|
14
|
+
# it, `Object` defines it or has a module prepended);
|
|
15
|
+
# - a `pre_eval:` patch defines `name` on `Object`, `Kernel` or `BasicObject`.
|
|
16
|
+
#
|
|
17
|
+
# A visibility toggle (`private :name`, `public :name`) does not change which def answers a receiverless call, and
|
|
18
|
+
# `method_missing` on `Object` only answers a name nothing defines, so neither is a hazard.
|
|
19
|
+
#
|
|
20
|
+
# The answer is a function of the discovery index and the name (the environment is the run's), so it is memoised
|
|
21
|
+
# per index. A recorder is told the `defines:<name>` name edge on every call, hit or not
|
|
22
|
+
# ({Scope#toplevel_def_ambiguity}), so a file that starts or stops defining the name re-checks the caller; the memo
|
|
23
|
+
# is bypassed while one is active, as the callee floor's other memos are.
|
|
24
|
+
module ToplevelDefCensus
|
|
25
|
+
OBJECT_OWNERS = %w[Object Kernel BasicObject].freeze
|
|
26
|
+
# ADR-129 WD1 — a run-scoped Memo, per discovery index and name: the answer is a function of both, and
|
|
27
|
+
# `MemoAudit` checks it on a hit.
|
|
28
|
+
RELIABLE = TypingState.memo(
|
|
29
|
+
:toplevel_def_reliable,
|
|
30
|
+
run_scoped: true, audit: "top-level def census",
|
|
31
|
+
reason: "keyed by discovery index, then name; the environment is the run's; audited by MemoAudit"
|
|
32
|
+
)
|
|
33
|
+
private_constant :OBJECT_OWNERS, :RELIABLE
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# True when the `<toplevel>` table's def for `name` is the method a receiverless call of `name` runs.
|
|
38
|
+
def reliable?(scope, name)
|
|
39
|
+
return compute?(scope, name) if Analysis::DependencyRecorder.active? || MemoAudit.suspended?
|
|
40
|
+
|
|
41
|
+
table = TypingState.current.weak_key_map(RELIABLE)
|
|
42
|
+
by_name = (table[scope.discovery] ||= {})
|
|
43
|
+
unless by_name.key?(name)
|
|
44
|
+
return by_name[name] = MemoAudit.key_audit("top-level def census") { compute?(scope, name) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
MemoAudit.check("top-level def census", by_name[name], name) { compute?(scope, name) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def compute?(scope, name)
|
|
51
|
+
return false unless scope.toplevel_def_ambiguity(name).nil?
|
|
52
|
+
|
|
53
|
+
!patched?(scope, name)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# True when a `pre_eval:` patch defines `name` on an `Object`-level class.
|
|
57
|
+
def patched?(scope, name)
|
|
58
|
+
patched = scope.environment&.project_patched_methods
|
|
59
|
+
return false if patched.nil?
|
|
60
|
+
|
|
61
|
+
patched.by_key.each_key.any? do |(owner, method_name, _kind)|
|
|
62
|
+
method_name == name && OBJECT_OWNERS.include?(owner)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -29,9 +29,10 @@ module Rigor
|
|
|
29
29
|
# written in; a refinement seed (`non-empty-array[0]`) still can, since its remover does not close a literal.
|
|
30
30
|
#
|
|
31
31
|
# A site whose widening declines leaves the binding as it is, exactly as the straight-line seam does: a
|
|
32
|
-
# precise nominal (a declared or inferred `Array[String]` is a claim
|
|
33
|
-
#
|
|
34
|
-
#
|
|
32
|
+
# precise nominal under a site that stores nothing it can see (a declared or inferred `Array[String]` is a claim
|
|
33
|
+
# this seam may not grow; an element rewriter such as `map!` replaces it with the gradual arm, issue #2427), a
|
|
34
|
+
# receiver that is no carrier, or a name the mutator tables do not list for that carrier (`store` on a `Tuple`, a
|
|
35
|
+
# method its class does not define). The caller MUST therefore not read an unchanged binding as describing later
|
|
35
36
|
# iterations. An empty-witness refinement under a storing mutator that keeps the witness is not such a decline:
|
|
36
37
|
# the straight-line widening grows it (issue #936), and gives a site whose arguments describe nothing it stores
|
|
37
38
|
# (`non-empty-array[String]` under `map!`) the gradual arm itself ({RewriteMutation}),
|
|
@@ -64,10 +65,11 @@ module Rigor
|
|
|
64
65
|
CalleeStore = Data.define(:call, :arguments)
|
|
65
66
|
|
|
66
67
|
COLLECTION_CLASSES = %w[Array Hash].freeze
|
|
68
|
+
CONTAINER_CLASSES = %w[Array Hash Set].freeze
|
|
67
69
|
FLOORED_CLASSES = %w[Array Hash String].freeze
|
|
68
70
|
|
|
69
71
|
NO_ARG_NODES = [].freeze
|
|
70
|
-
private_constant :COLLECTION_CLASSES, :FLOORED_CLASSES, :NO_ARG_NODES
|
|
72
|
+
private_constant :COLLECTION_CLASSES, :CONTAINER_CLASSES, :FLOORED_CLASSES, :NO_ARG_NODES
|
|
71
73
|
|
|
72
74
|
module_function
|
|
73
75
|
|
|
@@ -99,6 +101,103 @@ module Rigor
|
|
|
99
101
|
VALUE_PRESERVING.include?(method_name) && !literal_carrier?(type) ? widened : gradual_content(widened)
|
|
100
102
|
end
|
|
101
103
|
|
|
104
|
+
# Issue #2296 — what the caller's argument `type` can hold after a callee that mutates the parameter inside a
|
|
105
|
+
# block (`def poke(acc) = 2.times { |i| acc[i] = "x" }`). Nothing at the call says what the block stores, so
|
|
106
|
+
# each carrier takes one unknown store and keeps its element type beside the gradual arm, as a block-captured
|
|
107
|
+
# binding does ({.widen}): `[7, 9]` reads `Array[Dynamic[top] | Integer]`, `{ a: 1 }`
|
|
108
|
+
# `Hash[Dynamic[top] | Symbol, Dynamic[top] | Integer]`, `Set[1]` `Set[Dynamic[top] | Integer]`, a literal
|
|
109
|
+
# String `String`. Not {.content_floor}'s bare carrier: dropping the element type there widened
|
|
110
|
+
# `ScopeIndexer.finalize_constant_writes` to `Hash[untyped, untyped]` under sig-gen (#2295). A class-level
|
|
111
|
+
# nominal (`Hash[String, Set[String]]`) is a claim this seam may not grow, and is left alone; a value-pinned one
|
|
112
|
+
# (`Hash["a" | "b", 1]`) holds a first-store answer the unknown store falsifies, and takes the gradual arm, as a
|
|
113
|
+
# block's unknown-store binding does.
|
|
114
|
+
def callee_block_floor(type)
|
|
115
|
+
widened = widen_store(type, :[]=, unknown_arguments(2)) || type
|
|
116
|
+
widened = callee_set_floor(widened) || widened
|
|
117
|
+
dynamic_elements(value_pinned_collection?(widened) ? gradual_content(widened) : widened)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Issue #2296 — `type` after a callee that grows the parameter through a Set-only adder (`s.add(x)`,
|
|
121
|
+
# `s.merge(xs)`): a Set carrier takes one unknown store, and anything else, a Hash under `merge` included, is
|
|
122
|
+
# returned as nil (unchanged).
|
|
123
|
+
def callee_set_floor(type)
|
|
124
|
+
widened = widen_store(type, :add, unknown_arguments(1))
|
|
125
|
+
widened && dynamic_elements(widened)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Issue #2647 — `type` with each container element the unknown store widened (`Dynamic[top] | String`, which an
|
|
129
|
+
# element read back out fails a literal-typed parameter with, although the callee stored a value the call never
|
|
130
|
+
# shows) made a gradual `Dynamic[String]`: the floor may forget a fact, never claim the nominal class. An element
|
|
131
|
+
# that carries no `Dynamic[top]` arm (a class-level `Array[Integer]`, which the floor leaves alone) is returned as
|
|
132
|
+
# it is. Scoped to the callee floor's two entry points, so a local mutation's unknown-store widening keeps its
|
|
133
|
+
# answer.
|
|
134
|
+
def dynamic_elements(type)
|
|
135
|
+
return type unless needs_dynamic?(type)
|
|
136
|
+
|
|
137
|
+
case type
|
|
138
|
+
when Type::Union then Type::Combinator.union(*type.members.map { |member| dynamic_elements(member) })
|
|
139
|
+
when Type::Difference then Type::Combinator.difference(dynamic_elements(type.base), type.removed)
|
|
140
|
+
when Type::Nominal
|
|
141
|
+
return type unless CONTAINER_CLASSES.include?(type.class_name) && !type.type_args.empty?
|
|
142
|
+
|
|
143
|
+
Type::Combinator.nominal_of(type.class_name, type_args: type.type_args.map { |arg| dynamic_element(arg) })
|
|
144
|
+
else type
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# True when {.dynamic_elements} would change `type`: a container element with a `Dynamic[top]` member and a class
|
|
149
|
+
# member (neither a literal nor `Dynamic[top]`). Reads with `any?` only, so the common unchanged type is not
|
|
150
|
+
# rebuilt.
|
|
151
|
+
def needs_dynamic?(type)
|
|
152
|
+
case type
|
|
153
|
+
when Type::Union then type.members.any? { |member| needs_dynamic?(member) }
|
|
154
|
+
when Type::Difference then needs_dynamic?(type.base)
|
|
155
|
+
when Type::Nominal
|
|
156
|
+
CONTAINER_CLASSES.include?(type.class_name) && type.type_args.any? { |arg| needs_dynamic_element?(arg) }
|
|
157
|
+
else false
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def needs_dynamic_element?(arg)
|
|
162
|
+
return false unless arg.is_a?(Type::Union)
|
|
163
|
+
|
|
164
|
+
arg.members.any? { |member| gradual_top?(member) } &&
|
|
165
|
+
arg.members.any? { |member| !member.is_a?(Type::Constant) && !gradual_top?(member) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def gradual_top?(member) = Type::Combinator.untyped?(member)
|
|
169
|
+
|
|
170
|
+
# `Dynamic[top] | A | B` as `Dynamic[A | B]` for the class members; a literal member (a value-pinned key) and any
|
|
171
|
+
# other element stay as they are.
|
|
172
|
+
def dynamic_element(arg)
|
|
173
|
+
members = arg.is_a?(Type::Union) ? arg.members : [arg]
|
|
174
|
+
gradual, known = members.partition { |member| gradual_top?(member) }
|
|
175
|
+
return arg if gradual.empty?
|
|
176
|
+
|
|
177
|
+
literals, classes = known.partition { |member| member.is_a?(Type::Constant) }
|
|
178
|
+
return arg if classes.empty?
|
|
179
|
+
|
|
180
|
+
Type::Combinator.union(*literals, Type::Combinator.dynamic(Type::Combinator.union(*classes)))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def unknown_arguments(count) = Array.new(count) { Type::Combinator.untyped }
|
|
184
|
+
|
|
185
|
+
# False only when {.callee_block_floor} and {.callee_set_floor} are sure to leave `type` as it is: a type that is
|
|
186
|
+
# no String, Array, Hash or Set carrier in any form. A cheap gate, so a call passing only such locals skips the
|
|
187
|
+
# census closures those floors read.
|
|
188
|
+
def callee_floorable?(type)
|
|
189
|
+
case type
|
|
190
|
+
when Type::Union then type.members.any? { |member| callee_floorable?(member) }
|
|
191
|
+
when Type::Tuple, Type::HashShape, Type::Difference, Type::Refined, Type::Intersection then true
|
|
192
|
+
when Type::Constant then type.value.is_a?(String) || type.value.is_a?(::Set)
|
|
193
|
+
when Type::Nominal then FLOORABLE_NOMINALS.include?(type.class_name)
|
|
194
|
+
else false
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
FLOORABLE_NOMINALS = %w[Array Hash Set String].freeze
|
|
199
|
+
private_constant :FLOORABLE_NOMINALS
|
|
200
|
+
|
|
102
201
|
# Every `Array` / `Hash` / `String` carrier `type` can be as its bare carrier: `Array[Dynamic[top]]`,
|
|
103
202
|
# `Hash[Dynamic[top], Dynamic[top]]`, `String`. A literal, a nominal (a precise one included) and a refinement
|
|
104
203
|
# over one ({.carrier_class}) all floor; so `non-empty-string`, which a callee can empty, reads `String`.
|
|
@@ -124,9 +124,8 @@ module Rigor
|
|
|
124
124
|
|
|
125
125
|
# How a walk position reaches the exit scope: whether a local rebind there is threaded, whether an
|
|
126
126
|
# instance variable's is, whether a class variable's or global's is, the offset below which a rebind
|
|
127
|
-
# precedes a jump,
|
|
128
|
-
#
|
|
129
|
-
# other binding construct, false otherwise), and the locals a nested block shadows.
|
|
127
|
+
# precedes a jump, whether the position binds targets only (`:multi_assign` under a multi-assign,
|
|
128
|
+
# `:capture` under any other binding construct, false otherwise), and the locals a nested block shadows.
|
|
130
129
|
Position = Data.define(:local, :ivar, :other, :horizon, :binding, :shadowed)
|
|
131
130
|
private_constant :Position
|
|
132
131
|
|
|
@@ -57,7 +57,7 @@ module Rigor
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def candidate?(entry_type, joined_type)
|
|
60
|
-
|
|
60
|
+
Type::Combinator.untyped?(entry_type) &&
|
|
61
61
|
joined_type.is_a?(Type::Union) && joined_type.members.any?(Type::Dynamic)
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -5,6 +5,8 @@ require "prism"
|
|
|
5
5
|
require_relative "void_origin"
|
|
6
6
|
require_relative "../reflection"
|
|
7
7
|
require_relative "../source/node_children"
|
|
8
|
+
require_relative "../typing_state"
|
|
9
|
+
require_relative "../memo_audit"
|
|
8
10
|
|
|
9
11
|
module Rigor
|
|
10
12
|
module Inference
|
|
@@ -53,8 +55,8 @@ module Rigor
|
|
|
53
55
|
# cycle (nil). Memoising is therefore always sound, and the cycle guard only ever rejects a node that
|
|
54
56
|
# is still mid-computation on the current chain (never yet memoised).
|
|
55
57
|
class VoidTailSummary
|
|
56
|
-
# Run-scoped memo, the ADR-84 `return_memo_bucket` house pattern: a single retained slot
|
|
57
|
-
#
|
|
58
|
+
# Run-scoped memo, the ADR-84 `return_memo_bucket` house pattern: a single retained slot in
|
|
59
|
+
# the thread's {TypingState} keyed by the run-generation token's identity, so a re-run in one process (LSP,
|
|
58
60
|
# ADR-62 warm loop) lands in a fresh bucket and can neither serve stale entries nor accumulate
|
|
59
61
|
# them. Thread-local ⇒ each fork-pool worker (a separate process) and each Ractor-pool worker (its
|
|
60
62
|
# own Ractor-local `Thread.current`) builds its own bucket; the bucket hash is created and mutated
|
|
@@ -62,8 +64,15 @@ module Rigor
|
|
|
62
64
|
# graceful-degradation branch is needed. `def_node` identity is stable per run (the ADR-85
|
|
63
65
|
# {DefNodeResolver} yields one node object per `(path, node_id)`), the same identity the ADR-84
|
|
64
66
|
# return memo relies on.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
#
|
|
68
|
+
# ADR-129 WD1 — a run-scoped Memo keyed on the token's identity: the bucket holds summaries that are pure
|
|
69
|
+
# functions of the def node they key on within one run.
|
|
70
|
+
MEMO = TypingState.memo(
|
|
71
|
+
:void_tail_summary,
|
|
72
|
+
run_scoped: true, audit: ["void tail origin", "void tail walk"],
|
|
73
|
+
reason: "a bucket of per-def-node summaries, keyed on the run-generation token's identity"
|
|
74
|
+
)
|
|
75
|
+
private_constant :MEMO
|
|
67
76
|
|
|
68
77
|
def initialize(scope)
|
|
69
78
|
@scope = scope
|
|
@@ -81,7 +90,11 @@ module Rigor
|
|
|
81
90
|
return nil if def_node.nil?
|
|
82
91
|
|
|
83
92
|
memo = memo_bucket
|
|
84
|
-
|
|
93
|
+
if memo.key?(def_node)
|
|
94
|
+
return MemoAudit.check("void tail origin", memo[def_node], def_node) do
|
|
95
|
+
walk(def_node, owner, kind, {}.compare_by_identity)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
85
98
|
|
|
86
99
|
walk(def_node, owner, kind, {}.compare_by_identity)
|
|
87
100
|
end
|
|
@@ -97,10 +110,14 @@ module Rigor
|
|
|
97
110
|
return nil if visited.key?(def_node)
|
|
98
111
|
|
|
99
112
|
memo = memo_bucket
|
|
100
|
-
|
|
113
|
+
if memo.key?(def_node)
|
|
114
|
+
return MemoAudit.check("void tail walk", memo[def_node], def_node) do
|
|
115
|
+
walk(def_node, owner, kind, {}.compare_by_identity)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
101
118
|
|
|
102
119
|
visited[def_node] = true
|
|
103
|
-
memo[def_node] = compute(def_node, owner, kind, visited)
|
|
120
|
+
memo[def_node] = MemoAudit.key_audit("void tail walk") { compute(def_node, owner, kind, visited) }
|
|
104
121
|
end
|
|
105
122
|
|
|
106
123
|
def compute(def_node, owner, kind, visited)
|
|
@@ -205,11 +222,14 @@ module Rigor
|
|
|
205
222
|
end
|
|
206
223
|
|
|
207
224
|
def memo_bucket
|
|
225
|
+
return {}.compare_by_identity if MemoAudit.enabled? && MemoAudit.suspended?
|
|
226
|
+
|
|
208
227
|
generation = scope.run_generation || scope.discovered_def_nodes
|
|
209
|
-
|
|
228
|
+
state = TypingState.current
|
|
229
|
+
slot = state[MEMO]
|
|
210
230
|
unless slot && slot[0].equal?(generation)
|
|
211
231
|
slot = [generation, {}.compare_by_identity]
|
|
212
|
-
|
|
232
|
+
state[MEMO] = slot
|
|
213
233
|
end
|
|
214
234
|
slot[1]
|
|
215
235
|
end
|
|
@@ -13,9 +13,9 @@ module Rigor
|
|
|
13
13
|
# with no `range`) stays supported — it is still legal under incremental sync and is what `didOpen` and a
|
|
14
14
|
# client-side resync send.
|
|
15
15
|
class BufferTable
|
|
16
|
-
# @!attribute uri
|
|
17
|
-
# @!attribute bytes
|
|
18
|
-
# @!attribute version
|
|
16
|
+
# @!attribute uri — the LSP DocumentUri (e.g. `file:///abs/path/lib/foo.rb`).
|
|
17
|
+
# @!attribute bytes — the current full text of the buffer.
|
|
18
|
+
# @!attribute version — the monotonically increasing LSP version number.
|
|
19
19
|
Entry = Data.define(:uri, :bytes, :version)
|
|
20
20
|
|
|
21
21
|
def initialize
|
|
@@ -42,7 +42,7 @@ module Rigor
|
|
|
42
42
|
@project_context = project_context
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
# @return LSP
|
|
45
|
+
# @return LSP completion items, or nil
|
|
46
46
|
# when the cursor isn't at a position the provider can
|
|
47
47
|
# enumerate completions for. Returning nil maps to
|
|
48
48
|
# `result: null` per the LSP spec — clients treat it as
|
|
@@ -65,19 +65,20 @@ module Rigor
|
|
|
65
65
|
line: locate_at[0] + 1, character: locate_at[1] + 1)
|
|
66
66
|
return nil if node.nil?
|
|
67
67
|
|
|
68
|
+
query = ScopeQuery.new(path: path, bytes: bytes_to_parse, index: nil)
|
|
68
69
|
case node
|
|
69
70
|
when Prism::CallNode
|
|
70
71
|
# `hash[:|` patches to `hash[:KEY_SENTINEL]`, an index access call whose argument is the sentinel
|
|
71
72
|
# symbol; NodeLocator at the sentinel returns the inner CallNode for `[]`. Hash-key completion wins
|
|
72
73
|
# when the call is an index access on a HashShape carrier; otherwise fall through to method
|
|
73
74
|
# completion.
|
|
74
|
-
hash_key_completion_for(node, parse_result.value,
|
|
75
|
-
method_completion_for(node, parse_result.value,
|
|
75
|
+
hash_key_completion_for(node, parse_result.value, query) ||
|
|
76
|
+
method_completion_for(node, parse_result.value, query)
|
|
76
77
|
when Prism::SymbolNode
|
|
77
78
|
# The sentinel symbol's NodeLocator hit; walk up via the AST to find the enclosing `[]` call.
|
|
78
|
-
hash_key_completion_for_symbol(node, parse_result.value,
|
|
79
|
+
hash_key_completion_for_symbol(node, parse_result.value, query)
|
|
79
80
|
when Prism::ConstantPathNode
|
|
80
|
-
constant_path_completion_for(node
|
|
81
|
+
constant_path_completion_for(node)
|
|
81
82
|
end
|
|
82
83
|
end
|
|
83
84
|
|
|
@@ -141,11 +142,11 @@ module Rigor
|
|
|
141
142
|
nil
|
|
142
143
|
end
|
|
143
144
|
|
|
144
|
-
def method_completion_for(call_node, root,
|
|
145
|
+
def method_completion_for(call_node, root, query)
|
|
145
146
|
receiver_node = call_node.receiver
|
|
146
147
|
return nil if receiver_node.nil? # implicit-self — slice-3 territory of completion
|
|
147
148
|
|
|
148
|
-
index = build_scope_index(root,
|
|
149
|
+
index = build_scope_index(root, query)
|
|
149
150
|
receiver_scope = index[receiver_node]
|
|
150
151
|
receiver_type = receiver_scope.type_of(receiver_node)
|
|
151
152
|
method_completions(receiver_type, receiver_scope)
|
|
@@ -155,13 +156,13 @@ module Rigor
|
|
|
155
156
|
# `[]` call AND the receiver's inferred type is a `Type::HashShape`. Returns nil for any other receiver
|
|
156
157
|
# carrier so the dispatcher falls through to method completion (which still surfaces Hash's methods for
|
|
157
158
|
# genuine method calls on a hash receiver).
|
|
158
|
-
def hash_key_completion_for(call_node, root,
|
|
159
|
+
def hash_key_completion_for(call_node, root, query)
|
|
159
160
|
return nil unless call_node.name == :[]
|
|
160
161
|
|
|
161
162
|
receiver_node = call_node.receiver
|
|
162
163
|
return nil if receiver_node.nil?
|
|
163
164
|
|
|
164
|
-
index = build_scope_index(root,
|
|
165
|
+
index = build_scope_index(root, query)
|
|
165
166
|
receiver_type = index[receiver_node].type_of(receiver_node)
|
|
166
167
|
return nil unless receiver_type.is_a?(Type::HashShape)
|
|
167
168
|
|
|
@@ -171,11 +172,11 @@ module Rigor
|
|
|
171
172
|
# When NodeLocator returns the sentinel SymbolNode directly (rather than the enclosing CallNode), walk
|
|
172
173
|
# up the AST for the smallest `[]` call containing the symbol's location. Re-walks the root once; cheap
|
|
173
174
|
# for LSP-sized buffers.
|
|
174
|
-
def hash_key_completion_for_symbol(symbol_node, root,
|
|
175
|
+
def hash_key_completion_for_symbol(symbol_node, root, query)
|
|
175
176
|
call_node = enclosing_index_call(root, symbol_node)
|
|
176
177
|
return nil if call_node.nil?
|
|
177
178
|
|
|
178
|
-
hash_key_completion_for(call_node, root,
|
|
179
|
+
hash_key_completion_for(call_node, root, query)
|
|
179
180
|
end
|
|
180
181
|
|
|
181
182
|
def enclosing_index_call(root, symbol_node)
|
|
@@ -212,11 +213,11 @@ module Rigor
|
|
|
212
213
|
# resolves to a class / module FQN; we enumerate every known class whose name is an immediate child of
|
|
213
214
|
# that parent. Top-level constants (`::Foo`) and parent-less paths are not yet supported (queued for
|
|
214
215
|
# slice 6 follow-up).
|
|
215
|
-
def constant_path_completion_for(const_path_node
|
|
216
|
+
def constant_path_completion_for(const_path_node)
|
|
216
217
|
parent_fqn = parent_fqn_of(const_path_node)
|
|
217
218
|
return nil if parent_fqn.nil?
|
|
218
219
|
|
|
219
|
-
scope =
|
|
220
|
+
scope = Scope.empty(environment: @project_context.environment) # reads only the environment
|
|
220
221
|
children = enumerate_constant_children(parent_fqn, scope)
|
|
221
222
|
return nil if children.empty?
|
|
222
223
|
|
|
@@ -277,8 +278,14 @@ module Rigor
|
|
|
277
278
|
}
|
|
278
279
|
end
|
|
279
280
|
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
# The (path, bytes) a scope query is seeded from: the bytes the parse saw, sentinel splice included. `index`
|
|
282
|
+
# memoises the request's scope index, so a `hash[...]` request that falls through from hash-key to method
|
|
283
|
+
# completion builds it once.
|
|
284
|
+
ScopeQuery = Struct.new(:path, :bytes, :index)
|
|
285
|
+
private_constant :ScopeQuery
|
|
286
|
+
|
|
287
|
+
def seeded_scope(query)
|
|
288
|
+
@project_context.seeded_scope_for(path: query.path, bytes: query.bytes)
|
|
282
289
|
end
|
|
283
290
|
|
|
284
291
|
def locate_node(source:, root:, line:, character:)
|
|
@@ -287,12 +294,11 @@ module Rigor
|
|
|
287
294
|
nil
|
|
288
295
|
end
|
|
289
296
|
|
|
290
|
-
def build_scope_index(root,
|
|
291
|
-
|
|
292
|
-
Inference::ScopeIndexer.index(root, default_scope: scope)
|
|
297
|
+
def build_scope_index(root, query)
|
|
298
|
+
query.index ||= Inference::ScopeIndexer.index(root, default_scope: seeded_scope(query))
|
|
293
299
|
end
|
|
294
300
|
|
|
295
|
-
# Returns
|
|
301
|
+
# Returns the LSP completion items for every public method callable on the receiver.
|
|
296
302
|
# Slice B3 extends the slice-B1 floor with:
|
|
297
303
|
# - `Type::Refined` / `Type::Difference` — enumerate the underlying nominal (refinement narrows the
|
|
298
304
|
# value set, not the method set).
|
|
@@ -312,7 +318,7 @@ module Rigor
|
|
|
312
318
|
end
|
|
313
319
|
end
|
|
314
320
|
|
|
315
|
-
# Returns
|
|
321
|
+
# Returns the receiver's method table with its dispatch kind (`:instance` or `:singleton`). Composite
|
|
316
322
|
# carriers (Union / Intersection / Refined / shape carriers) reduce to instance-method enumeration; the
|
|
317
323
|
# receiver-class label that lands in each CompletionItem's `detail` still comes from each method's own
|
|
318
324
|
# `defs.first.implemented_in`, so the rendered prefix stays accurate per-method.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module LanguageServer
|
|
5
|
+
# Issue #2231 — shows each {ConfigAudit} warning the editor has not seen yet as a `window/showMessage`
|
|
6
|
+
# (`MessageType.Warning`), worded as `rigor check` prints it on STDERR. {ProjectContext#unannounced_config_warnings}
|
|
7
|
+
# decides what is new, so a call after a reload that changed nothing writes nothing. Called on `initialized` and
|
|
8
|
+
# after each save round's configuration reload (`DiagnosticPublisher`).
|
|
9
|
+
class ConfigWarningAnnouncer
|
|
10
|
+
# LSP `MessageType.Warning`, the `type` of a `window/showMessage` notification.
|
|
11
|
+
MESSAGE_TYPE_WARNING = 2
|
|
12
|
+
|
|
13
|
+
def initialize(writer:, project_context:)
|
|
14
|
+
@writer = writer
|
|
15
|
+
@project_context = project_context
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def announce
|
|
19
|
+
return unless @project_context.respond_to?(:unannounced_config_warnings)
|
|
20
|
+
|
|
21
|
+
@project_context.unannounced_config_warnings.each do |warning|
|
|
22
|
+
@writer.write(method: "window/showMessage",
|
|
23
|
+
params: { type: MESSAGE_TYPE_WARNING, message: "rigor: #{warning.message}" })
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|