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
|
@@ -9,9 +9,10 @@ module Rigor
|
|
|
9
9
|
#
|
|
10
10
|
# The mapping from Rails column types to Ruby class names is deliberately conservative —
|
|
11
11
|
# `:string`/`:text` → `String`, `:integer`/`:bigint` → `Integer`, `:boolean` → `bool`,
|
|
12
|
-
# `:datetime`/`:timestamp` → `Time`, `:date` → `Date`, `:
|
|
13
|
-
#
|
|
14
|
-
#
|
|
12
|
+
# `:datetime`/`:timestamp` → `Time`, `:date` → `Date`, `:float` → `Float`. A `:decimal` column's class
|
|
13
|
+
# follows its scale, so it is typed by {.decimal_ruby_type} from the parser that reads the scale, and
|
|
14
|
+
# reads `Object` here. Exotic types (json, jsonb, ltree, hstore, custom) fall back to `Object` so the
|
|
15
|
+
# plugin stays silent rather than guessing.
|
|
15
16
|
class SchemaTable
|
|
16
17
|
Column = Struct.new(:name, :type, :ruby_type, :array, keyword_init: true) do
|
|
17
18
|
def to_h = { name: name, type: type, ruby_type: ruby_type, array: array }
|
|
@@ -21,14 +22,16 @@ module Rigor
|
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
# Map ActiveRecord column types → Ruby class names.
|
|
25
25
|
RUBY_TYPE_MAPPING = {
|
|
26
26
|
string: "String",
|
|
27
27
|
text: "String",
|
|
28
28
|
integer: "Integer",
|
|
29
29
|
bigint: "Integer",
|
|
30
|
+
# PostgreSQL's dumper writes `id: :serial` / `t.serial` for a sequence-backed integer column.
|
|
31
|
+
serial: "Integer",
|
|
32
|
+
bigserial: "Integer",
|
|
30
33
|
float: "Float",
|
|
31
|
-
decimal: "
|
|
34
|
+
decimal: "Object",
|
|
32
35
|
boolean: "bool",
|
|
33
36
|
datetime: "Time",
|
|
34
37
|
timestamp: "Time",
|
|
@@ -46,7 +49,7 @@ module Rigor
|
|
|
46
49
|
}.freeze
|
|
47
50
|
|
|
48
51
|
# Implicit columns that every Rails table has unless the schema explicitly opts out. The plugin
|
|
49
|
-
# assumes these exist; users who run `create_table id: false` get no implicit `id` column from
|
|
52
|
+
# assumes these exist; users who run `create_table id: false` or `id: nil` get no implicit `id` column from
|
|
50
53
|
# the parser, but most apps never disable it.
|
|
51
54
|
IMPLICIT_COLUMNS = [
|
|
52
55
|
Column.new(name: "id", type: :integer, ruby_type: "Integer").freeze
|
|
@@ -84,6 +87,17 @@ module Rigor
|
|
|
84
87
|
def self.ruby_type_for(column_type)
|
|
85
88
|
RUBY_TYPE_MAPPING.fetch(column_type.to_sym, "Object")
|
|
86
89
|
end
|
|
90
|
+
|
|
91
|
+
# Maps a `decimal` column's scale to its Ruby class. Rails registers `Type::DecimalWithoutScale`
|
|
92
|
+
# (an Integer) at scale 0 and `Type::Decimal` (a BigDecimal) at any other scale. An absent or
|
|
93
|
+
# non-literal scale (`nil`) is left to the adapter, which decides it, so it reads as `Object`.
|
|
94
|
+
def self.decimal_ruby_type(scale)
|
|
95
|
+
case scale
|
|
96
|
+
when 0 then "Integer"
|
|
97
|
+
when Integer then "BigDecimal"
|
|
98
|
+
else "Object"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
87
101
|
end
|
|
88
102
|
end
|
|
89
103
|
end
|
|
@@ -138,21 +138,23 @@ module Rigor
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
def build_column(name, rest)
|
|
141
|
-
type_str, array = extract_type(rest)
|
|
141
|
+
type_str, array, scale = extract_type(rest)
|
|
142
142
|
return nil if type_str.empty?
|
|
143
143
|
|
|
144
144
|
rails_type = PG_TYPE_TO_RAILS.fetch(type_str, type_str.to_sym)
|
|
145
|
+
ruby_type = rails_type == :decimal ? SchemaTable.decimal_ruby_type(scale) : SchemaTable.ruby_type_for(rails_type)
|
|
145
146
|
SchemaTable::Column.new(
|
|
146
147
|
name: name,
|
|
147
148
|
type: rails_type,
|
|
148
|
-
ruby_type:
|
|
149
|
+
ruby_type: ruby_type,
|
|
149
150
|
array: array
|
|
150
151
|
)
|
|
151
152
|
end
|
|
152
153
|
|
|
153
154
|
# The type is every token before the first uppercase-initial token (a column modifier such as
|
|
154
155
|
# `NOT`, `NULL`, `DEFAULT`, `GENERATED`, `COLLATE`). A trailing `[]` marks a Postgres array column;
|
|
155
|
-
# a `(length[,scale])` specifier is dropped. Returns `[normalized_type, array
|
|
156
|
+
# a `(length[,scale])` specifier is dropped. Returns `[normalized_type, array?, scale]`, where `scale` is
|
|
157
|
+
# the literal second number of a `(precision, scale)` specifier, or nil when there is none.
|
|
156
158
|
def extract_type(rest)
|
|
157
159
|
type_tokens = []
|
|
158
160
|
rest.split(/\s+/).each do |token|
|
|
@@ -162,8 +164,9 @@ module Rigor
|
|
|
162
164
|
end
|
|
163
165
|
type_str = type_tokens.join(" ")
|
|
164
166
|
array = type_str.include?("[]")
|
|
167
|
+
scale = type_str[/\(\s*\d+\s*,\s*(\d+)\s*\)/, 1]&.to_i
|
|
165
168
|
type_str = type_str.gsub("[]", "").gsub(/\([0-9,\s]*\)/, "").strip.downcase
|
|
166
|
-
[type_str, array]
|
|
169
|
+
[type_str, array, scale]
|
|
167
170
|
end
|
|
168
171
|
end
|
|
169
172
|
end
|
|
@@ -59,12 +59,20 @@ module Rigor
|
|
|
59
59
|
# capital R) is intentional — keeps the constant lookup distinct from `::ActiveRecord` even though the
|
|
60
60
|
# gem name is hyphenated.
|
|
61
61
|
#
|
|
62
|
-
# Note: this plugin
|
|
63
|
-
#
|
|
62
|
+
# Note: this plugin does NOT require `active_record` at runtime — it only reads project source, the
|
|
63
|
+
# same way the other examples do. Rigor stays decoupled from Rails.
|
|
64
64
|
class Activerecord < Rigor::Plugin::Base
|
|
65
65
|
manifest(
|
|
66
66
|
id: "activerecord",
|
|
67
67
|
target_gems: ["activerecord"],
|
|
68
|
+
# 0.16.0, 2026-11-13 (#2956) — the `id` column takes the primary key's type (`id: :uuid` reads `String`)
|
|
69
|
+
# instead of always `Integer`, and `serial` / `bigserial` columns read `Integer`. The schema table
|
|
70
|
+
# payload changes, so the bump follows the 0.15.0 entry.
|
|
71
|
+
#
|
|
72
|
+
# 0.15.0, 2026-11-07 (#2945) — `create_table`'s `primary_key: "name"` registers that column, so a query
|
|
73
|
+
# on it, or on an `alias_attribute` targeting it, no longer reports `unknown-column`. The schema table
|
|
74
|
+
# payload changes, and the bump (part of the producer cache KEY) stops a warm run serving the old one.
|
|
75
|
+
#
|
|
68
76
|
# 0.14.0, 2026-10-10 (#1721) — the plugin publishes `:reachability_roots`: the models ActiveRecord loads
|
|
69
77
|
# by name from an association. The association rows gain `macro`, `class_name_option`, `through` and
|
|
70
78
|
# `source_type_option`, so a cached 0.13.0 payload lacks the fields the roots are derived from and
|
|
@@ -124,7 +132,7 @@ module Rigor
|
|
|
124
132
|
# a scope lambda body / class-method body now contributes `Relation[Model]` via `scope.self_type`
|
|
125
133
|
# instead of falling through to `Kernel#select` (the IO multiplexer, `Array[String]` return). Plus
|
|
126
134
|
# `:select` added to the relation-entry-point list.
|
|
127
|
-
version: "0.
|
|
135
|
+
version: "0.16.0",
|
|
128
136
|
description: "Types ActiveRecord finders against the project's db/schema.rb and AR models.",
|
|
129
137
|
config_schema: {
|
|
130
138
|
"schema_file" => { kind: :string, default: "db/schema.rb" },
|
|
@@ -491,8 +499,8 @@ module Rigor
|
|
|
491
499
|
# The project's OWN definition wins, and is not merely a precision preference: `def name` on the
|
|
492
500
|
# model — or on an ancestor it declares — is an override Ruby dispatches to, and the plugin tier sits
|
|
493
501
|
# ABOVE the engine's body-inference tiers, so answering here would displace the override's real return
|
|
494
|
-
# type with `Dynamic`. `
|
|
495
|
-
# discovery arm reads, and it fails toward declining (a budget-exhausted walk answers
|
|
502
|
+
# type with `Dynamic`. `Reflection.project_method_defined(source: :discovered)` is the same table the veto's own
|
|
503
|
+
# discovery arm reads, and it fails toward declining (a budget-exhausted walk answers `maybe`).
|
|
496
504
|
def implicit_self_instance_member_type(call_node, scope, index)
|
|
497
505
|
return nil if scope.nil?
|
|
498
506
|
return nil unless call_node.arguments.nil?
|
|
@@ -504,7 +512,9 @@ module Rigor
|
|
|
504
512
|
entry = index.find(self_type.class_name)
|
|
505
513
|
return nil if entry.nil?
|
|
506
514
|
return nil unless instance_member_name?(entry, call_node.name)
|
|
507
|
-
return nil
|
|
515
|
+
return nil unless Rigor::Reflection.project_method_defined(
|
|
516
|
+
self_type.class_name, call_node.name, kind: :instance, source: :discovered, scope: scope
|
|
517
|
+
).no?
|
|
508
518
|
return nil if rbs_answers_before_object?(self_type.class_name, call_node.name, scope)
|
|
509
519
|
|
|
510
520
|
Rigor::Type::Combinator.untyped
|
|
@@ -552,9 +562,22 @@ module Rigor
|
|
|
552
562
|
!entry.column(column_name).nil?
|
|
553
563
|
end
|
|
554
564
|
|
|
565
|
+
# A project `self.find` on the model or an ancestor, or a walk that ran out of budget before ruling one out
|
|
566
|
+
# (`maybe`): the veto fails toward declining, as the discovery walk always has.
|
|
567
|
+
def project_find_defined?(entry, scope)
|
|
568
|
+
project_singleton_defined?(entry, :find, scope)
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# The same veto for any class-side selector the plugin answers in Rails' name (`find`, `select`).
|
|
572
|
+
def project_singleton_defined?(entry, name, scope)
|
|
573
|
+
!Rigor::Reflection.project_method_defined(entry.class_name, name, kind: :singleton, source: :discovered,
|
|
574
|
+
scope: scope).no?
|
|
575
|
+
end
|
|
576
|
+
|
|
555
577
|
# Class-side finders + the class-side relation entry points. `find` returns the model, or an Array of
|
|
556
|
-
# them for two or more ids, or with a block the model or `nil`; `find_by!` returns the model;
|
|
557
|
-
# `all` / `order` / `limit` / `none` open a relation. The
|
|
578
|
+
# them for two or more ids, or with a block the model or `nil`; `find_by!` returns the model;
|
|
579
|
+
# `find_by` adds the `nil` arm; `where` / `all` / `order` / `limit` / `none` open a relation. The
|
|
580
|
+
# relation then carries its element type
|
|
558
581
|
# through any further chained query method via the bundled `ActiveRecord::Relation` RBS.
|
|
559
582
|
def finder_return_type(call_node, entry, scope)
|
|
560
583
|
case call_node.name
|
|
@@ -571,7 +594,7 @@ module Rigor
|
|
|
571
594
|
|
|
572
595
|
# A model that defines its own `self.find` (`find(owner, id)`) takes two arguments for its own
|
|
573
596
|
# reasons, so the Array reading of Rails' `find` is withheld and the project's method answers.
|
|
574
|
-
return nil if
|
|
597
|
+
return nil if project_find_defined?(entry, scope)
|
|
575
598
|
|
|
576
599
|
Rigor::Type::Combinator.nominal_of("Array", type_args: [model])
|
|
577
600
|
when :find_by!
|
|
@@ -583,13 +606,10 @@ module Rigor
|
|
|
583
606
|
Rigor::Type::Combinator.nominal_of(entry.class_name),
|
|
584
607
|
Rigor::Type::Combinator.constant_of(nil)
|
|
585
608
|
)
|
|
586
|
-
when :
|
|
587
|
-
|
|
588
|
-
#
|
|
589
|
-
#
|
|
590
|
-
# multiplexer, return `Array[String]`), masking the AR class-side relation entry point. The rest
|
|
591
|
-
# of the query DSL chains through the bundled `ActiveRecord::Relation` RBS once a relation is
|
|
592
|
-
# open.
|
|
609
|
+
when :select then class_select_return_type(call_node, entry, scope)
|
|
610
|
+
when :where, :all, :order, :limit, :none
|
|
611
|
+
# The rest of the query DSL chains through the bundled `ActiveRecord::Relation` RBS once a
|
|
612
|
+
# relation is open.
|
|
593
613
|
relation_of(entry.class_name)
|
|
594
614
|
when :table_name
|
|
595
615
|
table_name_return_type(entry)
|
|
@@ -600,6 +620,23 @@ module Rigor
|
|
|
600
620
|
end
|
|
601
621
|
end
|
|
602
622
|
|
|
623
|
+
# `:select` was added to close Mastodon's `scope :duplicate_uris, -> { select(:uri).group(:uri).having(...) }`
|
|
624
|
+
# shape: the implicit-self `select(:uri)` inside the scope lambda body had been resolving to `Kernel#select`
|
|
625
|
+
# (IO multiplexer, return `Array[String]`), masking the AR class-side relation entry point.
|
|
626
|
+
#
|
|
627
|
+
# `Model.select` is `all.select`, so a block makes it `Enumerable#select` over the loaded records, as the
|
|
628
|
+
# bundled `Relation#select` overload says (#1408), and the answer is an Array of the model. Fields next to a
|
|
629
|
+
# block answer the Array too, for the reason given on that overload. A model that defines its own
|
|
630
|
+
# `self.select` keeps its own answer for the block forms, as it does for `find`'s block and multi-id
|
|
631
|
+
# forms (`project_find_defined?`); the builder form stays the relation, as `find`'s one-id form stays the
|
|
632
|
+
# model.
|
|
633
|
+
def class_select_return_type(call_node, entry, scope)
|
|
634
|
+
return relation_of(entry.class_name) if call_node.block.nil?
|
|
635
|
+
return nil if project_singleton_defined?(entry, :select, scope)
|
|
636
|
+
|
|
637
|
+
Rigor::Type::Combinator.nominal_of("Array", type_args: [Rigor::Type::Combinator.nominal_of(entry.class_name)])
|
|
638
|
+
end
|
|
639
|
+
|
|
603
640
|
# `Model.find { |m| … }` / `Model.find(&:pred?)` never reaches the id lookup: `Core.find` passes a
|
|
604
641
|
# block to `super` (`all.find`), whose `Relation#find` does `return super if block_given?`, landing on
|
|
605
642
|
# `Enumerable#find` over the loaded records. So the answer is the element or `nil`, exactly what the
|
|
@@ -614,7 +651,7 @@ module Rigor
|
|
|
614
651
|
# `Relation#find` overload does. A project `self.find` keeps its own answer, as it does for the
|
|
615
652
|
# multi-id form.
|
|
616
653
|
def block_find_return_type(call_node, entry, scope)
|
|
617
|
-
return nil if
|
|
654
|
+
return nil if project_find_defined?(entry, scope)
|
|
618
655
|
return Rigor::Type::Combinator.untyped unless call_argument_count(call_node).zero?
|
|
619
656
|
|
|
620
657
|
Rigor::Type::Combinator.union(
|
|
@@ -231,12 +231,26 @@ module ActiveRecord
|
|
|
231
231
|
def left_outer_joins: (*untyped) -> self
|
|
232
232
|
%a{pure}
|
|
233
233
|
def references: (*untyped) -> self
|
|
234
|
-
# With a block, `select` is Enumerable
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
234
|
+
# With a block, `select` is `Enumerable#select` over the loaded
|
|
235
|
+
# records and returns an Array (`return super()` when a block is
|
|
236
|
+
# given, `relation/query_methods.rb:413` in activerecord 8.1.3.1).
|
|
237
|
+
# Fields and a block together raise `ArgumentError` there; Rails 4
|
|
238
|
+
# ignored the fields and returned the Array, so that form answers
|
|
239
|
+
# the Array too, which holds on either version. Without a block it
|
|
240
|
+
# is the query builder.
|
|
241
|
+
#
|
|
242
|
+
# `Array[Elem]` is a closed receiver, unlike this one: an
|
|
243
|
+
# ActiveSupport Array method on the result (`pluck`, `second`,
|
|
244
|
+
# `to_sentence`, `blank?`) resolves only with the
|
|
245
|
+
# `rigor-activesupport-core-ext` plugin enabled, and reports
|
|
246
|
+
# `call.undefined-method` without it, as it does after `to_a`.
|
|
247
|
+
#
|
|
248
|
+
# The `%a{pure}` is the builder form's. The block form's
|
|
249
|
+
# `io.db.read` comes from the plugin's `RELATION_MATERIALIZERS` row,
|
|
250
|
+
# which labels both forms today (#1318).
|
|
238
251
|
%a{pure}
|
|
239
|
-
def select: (*untyped) ->
|
|
252
|
+
def select: (*untyped) { (Elem) -> boolish } -> Array[Elem]
|
|
253
|
+
| (*untyped) -> self
|
|
240
254
|
%a{pure}
|
|
241
255
|
def reselect: (*untyped) -> self
|
|
242
256
|
%a{pure}
|
|
@@ -105,10 +105,9 @@ module Rigor
|
|
|
105
105
|
walk(node.body, inner_path, &) if node.body
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
# The full constant name a `class` / `module` declaration defines
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
# appended to the path (`class User` inside `module Admin` → `Admin::User`).
|
|
108
|
+
# The full constant name a `class` / `module` declaration defines. A ROOTED local name (`class ::User`)
|
|
109
|
+
# names the top-level constant whatever the nesting, so the lexical path is dropped; otherwise the
|
|
110
|
+
# name is appended to it (`class User` inside `module Admin` → `Admin::User`).
|
|
112
111
|
def declared_constant_name(local_name, lexical_path)
|
|
113
112
|
return local_name.delete_prefix("::") if local_name.start_with?("::")
|
|
114
113
|
|
|
@@ -8,7 +8,7 @@ module Rigor
|
|
|
8
8
|
#
|
|
9
9
|
# Each row is `{ name:, kind: }`:
|
|
10
10
|
#
|
|
11
|
-
# - `name` —
|
|
11
|
+
# - `name` — the attachment method name as the user invokes it (`"avatar"`).
|
|
12
12
|
# - `kind` — `:singular` (`has_one_attached`) or `:collection` (`has_many_attached`).
|
|
13
13
|
class AttachmentIndex
|
|
14
14
|
attr_reader :entries
|
|
@@ -89,13 +89,15 @@ module Rigor
|
|
|
89
89
|
# decline — they take side-effecting argument types that the RBS surface already covers.
|
|
90
90
|
dynamic_return receivers: -> { attachment_index&.class_names || [] } do |call_node, scope|
|
|
91
91
|
next nil unless call_node.is_a?(Prism::CallNode)
|
|
92
|
-
|
|
92
|
+
|
|
93
|
+
receiver = call_node.receiver
|
|
94
|
+
next nil if receiver.nil?
|
|
93
95
|
next nil unless call_node.arguments.nil?
|
|
94
96
|
|
|
95
97
|
index = attachment_index
|
|
96
98
|
next nil if index.nil? || index.empty?
|
|
97
99
|
|
|
98
|
-
receiver_type = scope.type_of(
|
|
100
|
+
receiver_type = scope.type_of(receiver)
|
|
99
101
|
next nil unless receiver_type.is_a?(Rigor::Type::Nominal)
|
|
100
102
|
|
|
101
103
|
# `AttachmentIndex#attachments_for` de-roots the query itself (#621) — no retry arm here.
|
|
@@ -137,19 +139,14 @@ module Rigor
|
|
|
137
139
|
nil
|
|
138
140
|
end
|
|
139
141
|
|
|
140
|
-
# Issue #1056 — "the attachment index did not load" is a fact about the run's INPUTS, not
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
# carried no once-guard at all — the `.uniq` collapsed repeats within one instance's list, but the
|
|
144
|
-
# whole list was re-emitted on every analysed file.
|
|
142
|
+
# Issue #1056 — "the attachment index did not load" is a fact about the run's INPUTS, not the file
|
|
143
|
+
# being analysed, so it is registered via {Plugin::Base#disclose_once} rather than returned from the
|
|
144
|
+
# per-file hook (which had no once-guard and re-emitted the list on every file).
|
|
145
145
|
#
|
|
146
|
-
# The `key`s carry an ordinal prefix because the engine emits
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
# time (the unbounded-growth shape #569 measured at 4.2 M retained strings on Redmine). The key, not
|
|
151
|
-
# the message, is the identity: a second refusal naming a different path collapses into the first row
|
|
152
|
-
# rather than adding one — the notice is "the index did not load", said once.
|
|
146
|
+
# The `key`s carry an ordinal prefix because the engine emits disclosures in KEY order (#1051), and
|
|
147
|
+
# a refused read must precede a discovery failure. `@load_errors` is keyed by the same string, which
|
|
148
|
+
# caps it: `#attachment_index` re-attempts the load per call site (#569). The key, not the message,
|
|
149
|
+
# is the identity, so a second refusal collapses into the first row.
|
|
153
150
|
def disclose_load_errors
|
|
154
151
|
@load_errors.each do |key, message|
|
|
155
152
|
disclose_once(key, message: message, severity: :warning, rule: "load-error")
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
# Entry point matching the `rigor-<id>` gem-name convention. The Plugin::Loader's `require_gem!` step
|
|
4
4
|
# calls `require "rigor-activestorage"`, and this file loads the plugin's actual implementation under
|
|
5
5
|
# the `rigor/plugin/` namespace so the require / register pair fires.
|
|
6
|
-
|
|
6
|
+
require_relative "rigor/plugin/activestorage"
|
data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Rigor
|
|
|
12
12
|
# the core_ext predicate surface — the single cheapest purity win in a Rails app, per WD10 — is issue
|
|
13
13
|
# #388 and lands in this plugin's shipped RBS, not here (`try` was audited and skipped: it dispatches
|
|
14
14
|
# to whatever method the caller names, so its purity is a fact about the call site, not about `try`).
|
|
15
|
-
#
|
|
15
|
+
# The `in_time_zone_rows` group is #388's own addition: `DateTime#in_time_zone` reads `Time.zone`
|
|
16
16
|
# through a default argument, a gap the RBS sweep found but can only fix here.
|
|
17
17
|
#
|
|
18
18
|
# ## The clock
|
data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class ActivesupportCoreExt < Rigor::Plugin::Base
|
|
8
|
+
# #2433 — the result type of `Hash#symbolize_keys` / `#to_options`.
|
|
9
|
+
#
|
|
10
|
+
# ActiveSupport implements them as `transform_keys { |key| key.to_sym rescue key }`, so a String or
|
|
11
|
+
# Symbol key becomes a Symbol and a key without `to_sym` (an Integer, a Float) survives unchanged. One
|
|
12
|
+
# RBS return type cannot say that: `Hash[Symbol, V]` reads an Integer key as a Symbol, and
|
|
13
|
+
# `Hash[Symbol | K, V]` makes a String-keyed hash fail a declared `Hash[Symbol, untyped]` parameter.
|
|
14
|
+
# So the RBS rows answer `Hash[untyped, V]` and this module maps each key member where it can.
|
|
15
|
+
#
|
|
16
|
+
# A key that is not a String or Symbol (an Integer, a project class) reads `Dynamic[top]`: no Rigor fact proves
|
|
17
|
+
# a class has no `to_sym` (a mixin, a `pre_eval` file or a reopening can add one), so a kept key could claim
|
|
18
|
+
# a type the program does not have. It declines (`nil`, the RBS row answers) when a receiver member is
|
|
19
|
+
# neither a `Hash[K, V]` nor `nil`. A `nil` member is dropped: `nil.symbolize_keys` raises, and the engine
|
|
20
|
+
# adds `nil` back for `&.`.
|
|
21
|
+
module SymbolizeKeys
|
|
22
|
+
HASH = "Hash"
|
|
23
|
+
SYMBOLIZED_CLASSES = %w[String Symbol].freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Answers `Hash[K', V]` (the union of such over the receiver's Hash members), or nil to decline.
|
|
28
|
+
def result(receiver_type, scope)
|
|
29
|
+
members = receiver_type.is_a?(Rigor::Type::Union) ? receiver_type.members : [receiver_type]
|
|
30
|
+
answers = members.reject { |member| nil_member?(member) }.map { |member| member_answer(member, scope) }
|
|
31
|
+
return nil if answers.empty? || answers.any?(:decline)
|
|
32
|
+
|
|
33
|
+
hashes = answers.reject { |answer| answer == :empty }
|
|
34
|
+
hashes.empty? ? nil : Rigor::Type::Combinator.union(*hashes)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# An empty closed shape is `{}`: it adds no key, so it drops out of the union rather than declining it.
|
|
38
|
+
def member_answer(member, scope)
|
|
39
|
+
return :empty if member.is_a?(Rigor::Type::HashShape) && member.closed? && member.pairs.empty?
|
|
40
|
+
|
|
41
|
+
key_type, value_type = hash_arguments(member)
|
|
42
|
+
return :decline if key_type.nil?
|
|
43
|
+
|
|
44
|
+
Rigor::Type::Combinator.nominal_of(HASH, type_args: [map_key(key_type, scope), value_type])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def nil_member?(type)
|
|
48
|
+
(type.is_a?(Rigor::Type::Constant) && type.value.nil?) ||
|
|
49
|
+
(type.is_a?(Rigor::Type::Nominal) && type.class_name == "NilClass")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def hash_arguments(type)
|
|
53
|
+
case type
|
|
54
|
+
when Rigor::Type::Nominal
|
|
55
|
+
type.class_name == HASH && type.type_args.size == 2 ? type.type_args : nil
|
|
56
|
+
when Rigor::Type::Difference
|
|
57
|
+
hash_arguments(type.base)
|
|
58
|
+
when Rigor::Type::HashShape
|
|
59
|
+
shape_arguments(type)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A closed, non-empty shape reads as `Hash[<its keys>, <union of its values>]`.
|
|
64
|
+
def shape_arguments(shape)
|
|
65
|
+
return nil if shape.open? || shape.pairs.empty?
|
|
66
|
+
|
|
67
|
+
keys = shape.pairs.keys.map { |key| Rigor::Type::Combinator.constant_of(key) }
|
|
68
|
+
[Rigor::Type::Combinator.union(*keys), Rigor::Type::Combinator.union(*shape.pairs.values)]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def map_key(type, scope)
|
|
72
|
+
members = type.is_a?(Rigor::Type::Union) ? type.members : [type]
|
|
73
|
+
Rigor::Type::Combinator.union(*members.map { |member| map_member(member, scope) })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def map_member(member, scope)
|
|
77
|
+
case member
|
|
78
|
+
when Rigor::Type::Constant
|
|
79
|
+
member.value.is_a?(String) || member.value.is_a?(Symbol) ? symbol : Rigor::Type::Combinator.untyped
|
|
80
|
+
when Rigor::Type::Nominal
|
|
81
|
+
symbolized_class?(member.class_name, scope) ? symbol : Rigor::Type::Combinator.untyped
|
|
82
|
+
else Rigor::Type::Combinator.untyped
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def symbol
|
|
87
|
+
Rigor::Type::Combinator.nominal_of("Symbol")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# String, Symbol and their subclasses go through `to_sym`.
|
|
91
|
+
def symbolized_class?(name, scope)
|
|
92
|
+
SYMBOLIZED_CLASSES.include?(name) ||
|
|
93
|
+
SYMBOLIZED_CLASSES.any? { |base| %i[equal subclass].include?(ordering(name, base, scope)) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def ordering(name, base, scope)
|
|
97
|
+
Rigor::Reflection.class_ordering(name, base, scope: scope)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "rigor/plugin"
|
|
4
4
|
|
|
5
5
|
require_relative "activesupport_core_ext/effects"
|
|
6
|
+
require_relative "activesupport_core_ext/symbolize_keys"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Plugin
|
|
@@ -43,12 +44,15 @@ module Rigor
|
|
|
43
44
|
manifest(
|
|
44
45
|
id: "activesupport-core-ext",
|
|
45
46
|
target_gems: ["activesupport"],
|
|
47
|
+
# Bumped 2026-10-27 (#2433) — `Hash#symbolize_keys` / `#to_options` map each key member through a
|
|
48
|
+
# `dynamic_return` hook instead of claiming `Hash[Symbol, V]`.
|
|
49
|
+
#
|
|
46
50
|
# Bumped 2026-09-10 (#534 item 7) — `ActiveSupport::Concern` is declared, so `extend
|
|
47
51
|
# ActiveSupport::Concern` resolves instead of reading as an unresolved constant.
|
|
48
52
|
#
|
|
49
53
|
# Bumped 2026-09-02 (#632) — `ActiveSupport::Duration`'s reader surface (`ago`/`to_i`/`iso8601`/…)
|
|
50
54
|
# is now declared, open_receivers-protected; see the class comment above.
|
|
51
|
-
version: "0.
|
|
55
|
+
version: "0.6.0",
|
|
52
56
|
description: "RBS bundle for the most-frequently-flagged ActiveSupport core_ext extensions, " \
|
|
53
57
|
"plus the `ActiveSupport::Duration` type of the numeric time multipliers and its " \
|
|
54
58
|
"reader surface.",
|
|
@@ -128,10 +132,12 @@ module Rigor
|
|
|
128
132
|
|
|
129
133
|
dynamic_return methods: DURATION_MULTIPLIERS do |call_node, scope|
|
|
130
134
|
next nil unless call_node.is_a?(Prism::CallNode)
|
|
131
|
-
|
|
135
|
+
|
|
136
|
+
receiver = call_node.receiver
|
|
137
|
+
next nil unless receiver # `day` with no receiver is somebody's own method
|
|
132
138
|
next nil unless call_node.arguments.nil?
|
|
133
139
|
next nil unless call_node.block.nil?
|
|
134
|
-
next nil unless numeric_receiver?(scope&.type_of(
|
|
140
|
+
next nil unless numeric_receiver?(scope&.type_of(receiver))
|
|
135
141
|
|
|
136
142
|
Rigor::Type::Combinator.nominal_of(DURATION_NOMINAL)
|
|
137
143
|
end
|
|
@@ -190,20 +196,35 @@ module Rigor
|
|
|
190
196
|
argument = duration_arithmetic_argument(call_node)
|
|
191
197
|
next nil if argument.nil?
|
|
192
198
|
|
|
193
|
-
# The ARGUMENT is checked before the receiver: it is the cheaper
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
# what makes the ordering worth anything: an argument that is no Duration operand at all
|
|
197
|
-
# (`"a" + b`, `list + other`) declines here and never types its receiver on this rule's behalf.
|
|
199
|
+
# The ARGUMENT is checked before the receiver: it is the cheaper test on the common shapes (`i + 1`,
|
|
200
|
+
# `n * 2`), and this rule is consulted on every `+` / `-` / `*`. An argument that is no Duration
|
|
201
|
+
# operand (`"a" + b`, `list + other`) declines here and never types its receiver on this rule's behalf.
|
|
198
202
|
argument_kind = duration_operand_kind(scope&.type_of(argument))
|
|
199
203
|
next nil if argument_kind.nil?
|
|
200
204
|
|
|
201
|
-
|
|
205
|
+
receiver = call_node.receiver
|
|
206
|
+
# Prism gives every parsed `+` / `-` / `*` call a receiver; the guard is for the RBS `Node?` type.
|
|
207
|
+
next nil if receiver.nil?
|
|
208
|
+
|
|
209
|
+
receiver_kind = duration_operand_kind(scope&.type_of(receiver))
|
|
202
210
|
next nil if receiver_kind.nil?
|
|
203
211
|
|
|
204
212
|
duration_arithmetic_result(call_node.name, receiver_kind, argument_kind)
|
|
205
213
|
end
|
|
206
214
|
|
|
215
|
+
# #2433 — `symbolize_keys` / `to_options` keep a key without `to_sym`, so the key type is mapped per
|
|
216
|
+
# member; see {SymbolizeKeys}. The RBS rows answer `Hash[untyped, V]` wherever this declines.
|
|
217
|
+
dynamic_return methods: %i[symbolize_keys to_options] do |call_node, scope|
|
|
218
|
+
next nil unless call_node.is_a?(Prism::CallNode)
|
|
219
|
+
|
|
220
|
+
receiver = call_node.receiver
|
|
221
|
+
next nil unless receiver
|
|
222
|
+
next nil unless call_node.arguments.nil?
|
|
223
|
+
|
|
224
|
+
# A block is ignored by `symbolize_keys`, so it does not decline.
|
|
225
|
+
SymbolizeKeys.result(scope&.type_of(receiver), scope)
|
|
226
|
+
end
|
|
227
|
+
|
|
207
228
|
# ADR-88 WD1 — both tables are static and the RBS bundle ships with the gem; nothing here reads a
|
|
208
229
|
# project file, so a project enabling this plugin stays incremental-capable.
|
|
209
230
|
def incremental_state_fingerprint
|