rigortype 0.4.6 → 0.4.7
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 +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- 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 +5 -2
- data/docs/handbook/11-sig-gen.md +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -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 +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- 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 +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -4
- 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_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- 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 +77 -71
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- 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 +24 -31
- data/lib/rigor/bleeding_edge.rb +3 -6
- 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/incremental_snapshot.rb +31 -12
- 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 +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -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 +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +108 -102
- 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 +18 -17
- 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 +12 -8
- 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 +84 -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 +1 -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 +209 -54
- data/lib/rigor/config_audit.rb +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- 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 +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -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/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_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +480 -67
- 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 +41 -10
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -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/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- 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 +1 -1
- data/lib/rigor/inference/def_return_typer.rb +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- data/lib/rigor/inference/definers_index.rb +106 -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/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- 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/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- 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 +1 -1
- 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/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 +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- 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 +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- 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/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 +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- data/lib/rigor/inference/optimistic_origin.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/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -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/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- 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 +135 -0
- data/lib/rigor/inference/scope_indexer.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- 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/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- 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 +9 -13
- data/lib/rigor/plugin/registry.rb +3 -3
- 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 +2 -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/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 +5 -3
- data/lib/rigor/rooted_glob.rb +15 -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 +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +365 -31
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +371 -58
- 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 +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +3 -5
- 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/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -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 +217 -37
- 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 +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/union.rb +1 -1
- 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/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 +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- 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 +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- 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 +7 -12
- 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 +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- 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 +4 -5
- 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-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 +2 -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 +1 -3
- 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 +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- 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 +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- 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/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- 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/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- 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/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- 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/03-baseline-and-bugs.md +12 -1
- metadata +36 -3
|
@@ -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
|
|
|
@@ -534,7 +533,7 @@ module Rigor
|
|
|
534
533
|
# (resolved through `registry.modules_by_symbol`; unknown
|
|
535
534
|
# symbols silently skipped per design decision (2)).
|
|
536
535
|
#
|
|
537
|
-
# Returns
|
|
536
|
+
# Returns the module names: the `always_included` ones first, then those for the symbol arguments in order.
|
|
538
537
|
def resolve_trait_modules(registry, call_node)
|
|
539
538
|
modules = registry.always_included.dup
|
|
540
539
|
positional_symbols(call_node, registry).each do |symbol|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../run_scoped_slots"
|
|
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
|
+
RELIABLE_KEY = RunScopedSlots.register(:rigor_toplevel_def_reliable)
|
|
27
|
+
private_constant :OBJECT_OWNERS, :RELIABLE_KEY
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# True when the `<toplevel>` table's def for `name` is the method a receiverless call of `name` runs.
|
|
32
|
+
def reliable?(scope, name)
|
|
33
|
+
return compute?(scope, name) if Analysis::DependencyRecorder.active? || MemoAudit.suspended?
|
|
34
|
+
|
|
35
|
+
table = (Thread.current[RELIABLE_KEY] ||= ObjectSpace::WeakKeyMap.new)
|
|
36
|
+
by_name = (table[scope.discovery] ||= {})
|
|
37
|
+
return by_name[name] = compute?(scope, name) unless by_name.key?(name)
|
|
38
|
+
|
|
39
|
+
MemoAudit.check("top-level def census", by_name[name], name) { compute?(scope, name) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def compute?(scope, name)
|
|
43
|
+
return false unless scope.toplevel_def_ambiguity(name).nil?
|
|
44
|
+
|
|
45
|
+
!patched?(scope, name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# True when a `pre_eval:` patch defines `name` on an `Object`-level class.
|
|
49
|
+
def patched?(scope, name)
|
|
50
|
+
patched = scope.environment&.project_patched_methods
|
|
51
|
+
return false if patched.nil?
|
|
52
|
+
|
|
53
|
+
patched.by_key.each_key.any? do |(owner, method_name, _kind)|
|
|
54
|
+
method_name == name && OBJECT_OWNERS.include?(owner)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
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) = member.is_a?(Type::Dynamic) && member.static_facet.is_a?(Type::Top)
|
|
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
|
|
|
@@ -5,6 +5,7 @@ require "prism"
|
|
|
5
5
|
require_relative "void_origin"
|
|
6
6
|
require_relative "../reflection"
|
|
7
7
|
require_relative "../source/node_children"
|
|
8
|
+
require_relative "../run_scoped_slots"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
10
11
|
module Inference
|
|
@@ -62,7 +63,7 @@ module Rigor
|
|
|
62
63
|
# graceful-degradation branch is needed. `def_node` identity is stable per run (the ADR-85
|
|
63
64
|
# {DefNodeResolver} yields one node object per `(path, node_id)`), the same identity the ADR-84
|
|
64
65
|
# return memo relies on.
|
|
65
|
-
MEMO_KEY = :__rigor_void_tail_summary_memo__
|
|
66
|
+
MEMO_KEY = RunScopedSlots.register(:__rigor_void_tail_summary_memo__)
|
|
66
67
|
private_constant :MEMO_KEY
|
|
67
68
|
|
|
68
69
|
def initialize(scope)
|
|
@@ -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
|
|
@@ -4,6 +4,7 @@ require "tempfile"
|
|
|
4
4
|
|
|
5
5
|
require_relative "uri"
|
|
6
6
|
require_relative "publish_batcher"
|
|
7
|
+
require_relative "config_warning_announcer"
|
|
7
8
|
require_relative "../analysis/runner"
|
|
8
9
|
require_relative "../analysis/buffer_binding"
|
|
9
10
|
|
|
@@ -19,7 +20,7 @@ module Rigor
|
|
|
19
20
|
# buffers' debounce timers elapse around the same moment, the `PublishBatcher` coalesces them into one
|
|
20
21
|
# `#publish_many` round dispatched across the fork-based worker pool (issue #142) instead of N
|
|
21
22
|
# independent, GVL-serialized `Runner` calls.
|
|
22
|
-
class DiagnosticPublisher
|
|
23
|
+
class DiagnosticPublisher # rubocop:disable Metrics/ClassLength
|
|
23
24
|
# Maps Rigor severity symbols to LSP DiagnosticSeverity
|
|
24
25
|
# integers per spec § "Diagnostic":
|
|
25
26
|
# 1 = Error, 2 = Warning, 3 = Information, 4 = Hint.
|
|
@@ -51,6 +52,7 @@ module Rigor
|
|
|
51
52
|
@project_context = project_context
|
|
52
53
|
@debouncer = debouncer
|
|
53
54
|
@debounce_seconds = debounce_seconds
|
|
55
|
+
@config_warning_announcer = ConfigWarningAnnouncer.new(writer: writer, project_context: project_context)
|
|
54
56
|
@round_lock = Mutex.new
|
|
55
57
|
@round_running = false
|
|
56
58
|
@round_pending = false
|
|
@@ -130,6 +132,9 @@ module Rigor
|
|
|
130
132
|
notify(uri, [])
|
|
131
133
|
end
|
|
132
134
|
|
|
135
|
+
# Issue #2231 — shows the configuration warnings the editor has not seen yet ({ConfigWarningAnnouncer}).
|
|
136
|
+
def announce_config_warnings = @config_warning_announcer.announce
|
|
137
|
+
|
|
133
138
|
# Cancels every in-flight debounced task. Called from `Server#handle_shutdown` so pending publishes
|
|
134
139
|
# don't fire against a closed STDOUT.
|
|
135
140
|
def cancel_pending
|
|
@@ -167,8 +172,7 @@ module Rigor
|
|
|
167
172
|
|
|
168
173
|
# True when a save arrived mid-round and the loop should run once more. Releases ownership otherwise.
|
|
169
174
|
# `next`, not `break`: inside `Mutex#synchronize`'s block, `break` returns from the SYNCHRONIZE call, so
|
|
170
|
-
# a `break`
|
|
171
|
-
# before this was split out.
|
|
175
|
+
# a `break` here would leave the caller's `loop` spinning forever.
|
|
172
176
|
def consume_pending
|
|
173
177
|
@round_lock.synchronize do
|
|
174
178
|
next(@round_running = false) unless @round_pending
|
|
@@ -185,6 +189,7 @@ module Rigor
|
|
|
185
189
|
def execute_project_round(saved_uri)
|
|
186
190
|
generation = @project_context.generation
|
|
187
191
|
diagnostics = @project_context.project_diagnostics
|
|
192
|
+
announce_config_warnings # the round re-read the configuration; the reload stands even if the result does not
|
|
188
193
|
return if generation != @project_context.generation
|
|
189
194
|
|
|
190
195
|
by_path = diagnostics.group_by(&:path)
|
|
@@ -30,7 +30,7 @@ module Rigor
|
|
|
30
30
|
@project_context = project_context
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
# @return LSP
|
|
33
|
+
# @return LSP document symbols for the
|
|
34
34
|
# buffer at `uri`. Returns nil when the URI isn't open or
|
|
35
35
|
# doesn't parse cleanly enough to surface symbols — LSP
|
|
36
36
|
# clients fall back to no-outline in that case.
|
|
@@ -23,8 +23,7 @@ module Rigor
|
|
|
23
23
|
@project_context = project_context
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# @return LSP
|
|
27
|
-
# buffer, or nil when the URI isn't open / parseable.
|
|
26
|
+
# @return LSP folding ranges for the buffer, or nil when the URI isn't open / parseable.
|
|
28
27
|
def provide(uri)
|
|
29
28
|
path, entry = buffer_for(uri)
|
|
30
29
|
return nil if entry.nil?
|
|
@@ -44,7 +44,7 @@ module Rigor
|
|
|
44
44
|
node = locate_node(source: entry.bytes, root: parse_result.value, line: line + 1, character: character + 1)
|
|
45
45
|
return nil if node.nil?
|
|
46
46
|
|
|
47
|
-
scope =
|
|
47
|
+
scope = @project_context.seeded_scope_for(path: path, bytes: entry.bytes)
|
|
48
48
|
index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope)
|
|
49
49
|
node_scope = index[node]
|
|
50
50
|
type = node_scope.type_of(node)
|
|
@@ -59,12 +59,6 @@ module Rigor
|
|
|
59
59
|
rescue Source::NodeLocator::OutOfRangeError
|
|
60
60
|
nil
|
|
61
61
|
end
|
|
62
|
-
|
|
63
|
-
def base_scope(_path)
|
|
64
|
-
# Pulls the Environment from the cached ProjectContext so each hover avoids repeating the RBS-load
|
|
65
|
-
# build.
|
|
66
|
-
Scope.empty(environment: @project_context.environment)
|
|
67
|
-
end
|
|
68
62
|
end
|
|
69
63
|
end
|
|
70
64
|
end
|
|
@@ -60,9 +60,8 @@ module Rigor
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# Renders a `CallNode` hover when the receiver's type can be mapped to a known RBS class and the method
|
|
63
|
-
# is declared there. Returns nil for shapes
|
|
64
|
-
# Refined / Shape receivers
|
|
65
|
-
# default body.
|
|
63
|
+
# is declared there. Returns nil for shapes this renderer does not handle (implicit `self`, Union /
|
|
64
|
+
# Refined / Shape receivers); the caller falls back to the default body.
|
|
66
65
|
def render_call(call_node, return_type, node_scope_lookup)
|
|
67
66
|
receiver_node = call_node.receiver
|
|
68
67
|
return nil if receiver_node.nil?
|
|
@@ -133,17 +132,16 @@ module Rigor
|
|
|
133
132
|
body << "#{return_type.describe}\n"
|
|
134
133
|
body << "```"
|
|
135
134
|
if (doc = rbs_documentation(definition))
|
|
136
|
-
#
|
|
137
|
-
# necessary — plain markdown below the code reads better in clients.
|
|
135
|
+
# The fence is already closed, so the RBS comment renders as plain markdown below the code.
|
|
138
136
|
body << "\n\n---\n\n#{doc}"
|
|
139
137
|
end
|
|
140
138
|
body
|
|
141
139
|
end
|
|
142
140
|
|
|
143
141
|
# Returns the concatenated text of every RBS comment attached to the method definition, or nil when no
|
|
144
|
-
# comments exist. `RBS::Definition::Method#comments` is
|
|
145
|
-
# `.string`
|
|
146
|
-
#
|
|
142
|
+
# comments exist. `RBS::Definition::Method#comments` is a list of `AST::Comment`s, each with its raw text
|
|
143
|
+
# in `.string` (newline-terminated `# foo` lines). Multiple comments are joined with a blank line so each
|
|
144
|
+
# upstream `# Foo bar` paragraph is preserved.
|
|
147
145
|
def rbs_documentation(definition)
|
|
148
146
|
comments = definition.respond_to?(:comments) ? definition.comments : nil
|
|
149
147
|
return nil if comments.nil? || comments.empty?
|