rigortype 0.4.5 → 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 +61 -5
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +192 -27
- data/docs/manual/03-configuration.md +38 -4
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +47 -3
- data/docs/manual/12-caching.md +9 -1
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +49 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +2 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- 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 +105 -12
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -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/rbs_receiver_surface.rb +66 -3
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- 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 +53 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +217 -52
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +15 -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 +154 -0
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +37 -0
- 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 +202 -43
- data/lib/rigor/analysis/path_expansion.rb +41 -0
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +7 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
- data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
- 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/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +233 -120
- 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 +38 -33
- 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 +60 -18
- 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 +188 -93
- 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 +27 -21
- 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 +17 -13
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +14 -8
- 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 +60 -4
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +22 -18
- data/lib/rigor/cli/plugins_renderer.rb +34 -8
- 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 +23 -16
- data/lib/rigor/cli/sig_gen_command.rb +130 -34
- 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 +18 -12
- data/lib/rigor/cli/type_of_project_scope.rb +24 -0
- 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 +17 -6
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +86 -34
- 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 +592 -70
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +136 -32
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +44 -11
- 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 +126 -0
- 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 +170 -0
- 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 +204 -12
- data/lib/rigor/inference/definer_resolution.rb +21 -5
- 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/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +173 -57
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +110 -1
- 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 +246 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +57 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +65 -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 +113 -12
- 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 +7 -3
- 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 +59 -24
- 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 +109 -29
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +23 -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 +162 -29
- data/lib/rigor/inference/narrowing.rb +113 -38
- data/lib/rigor/inference/object_mixins.rb +13 -1
- 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_seed_tables.rb +2 -1
- data/lib/rigor/inference/project_yielder.rb +122 -0
- 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 +9 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
- 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/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +614 -146
- data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
- 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 +3036 -278
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
- 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 +9 -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 +28 -9
- 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 +98 -4
- 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 +12 -6
- 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 +82 -3
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +26 -11
- 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 +38 -16
- data/lib/rigor/plugin/registry.rb +41 -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 +96 -2
- 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 +25 -2
- 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 +379 -36
- 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 +668 -84
- 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 +7 -6
- 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/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +21 -12
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- 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 +13 -16
- data/lib/rigor/type/constant.rb +48 -7
- data/lib/rigor/type/hash_shape.rb +26 -40
- data/lib/rigor/type/struct_instance.rb +3 -2
- 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 +89 -70
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -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 +79 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -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-devise/lib/rigor/plugin/devise.rb +5 -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 +7 -6
- 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 +102 -11
- 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 +5 -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/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +0 -2
- 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/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +10 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +6 -5
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +6 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/scope.rbs +51 -50
- data/sig/rigor/type.rbs +0 -6
- 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 +55 -3
|
@@ -28,8 +28,8 @@ module Rigor
|
|
|
28
28
|
#
|
|
29
29
|
# An `ActiveRecord::Base` row reaches `User.find` through the project's own
|
|
30
30
|
# `User < ApplicationRecord < ActiveRecord::Base` lines, so one row covers every model in the app.
|
|
31
|
-
# That is the whole economy of the framework layer:
|
|
32
|
-
# database touches a Rails app performs, and none of them needed a line of application code.
|
|
31
|
+
# That is the whole economy of the framework layer: a fixed set of rows here colours the several
|
|
32
|
+
# thousand database touches a Rails app performs, and none of them needed a line of application code.
|
|
33
33
|
module Effects
|
|
34
34
|
BASE = "ActiveRecord::Base"
|
|
35
35
|
RELATION = "ActiveRecord::Relation"
|
|
@@ -186,27 +186,20 @@ module Rigor
|
|
|
186
186
|
merge_redeclarations(rows)
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
# Collapses the rows that declare the SAME constant into one, so {ModelIndex.build}
|
|
190
|
-
#
|
|
189
|
+
# Collapses the rows that declare the SAME constant into one, so {ModelIndex.build} (keyed by
|
|
190
|
+
# `class_name`) receives at most one row per model.
|
|
191
191
|
#
|
|
192
|
-
# A model is routinely declared more than once
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
# rows are UNIONed here. Taking the last row instead dropped the real declaration's associations,
|
|
197
|
-
# scopes, enums, validations, callbacks and `alias_attribute`s whenever the reopen sorted later in
|
|
198
|
-
# the glob, and `where(<a declared alias>: …)` then surfaced a false `unknown-column` on correct
|
|
199
|
-
# code; taking the first dropped whatever the reopen added. Neither order loses anything now.
|
|
192
|
+
# A model is routinely declared more than once (`app/models/user.rb` plus a reopening file). Ruby's
|
|
193
|
+
# semantics are ADDITIVE, so the rows are UNIONed. Taking the last row dropped the real declaration's
|
|
194
|
+
# associations, scopes, enums, validations, callbacks and `alias_attribute`s whenever the reopen sorted
|
|
195
|
+
# later, producing false `unknown-column`s; taking the first dropped what the reopen added.
|
|
200
196
|
#
|
|
201
|
-
# Merge is by field: the first non-nil `superclass_name` wins
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# inexact); name-keyed rows (associations, enums, aliases) let the LAST declaration override an
|
|
208
|
-
# earlier same-name row exactly as {ModelIndex.merge_named_rows} does across an STI chain, and the
|
|
209
|
-
# plain lists union.
|
|
197
|
+
# Merge is by field: the first non-nil `superclass_name` wins. `self.table_name =` is an ASSIGNMENT, so
|
|
198
|
+
# the later `table_name_override` wins, but when two declarations disagree the name is marked computed
|
|
199
|
+
# so no value is pinned: glob order is not load order, and a pinned wrong literal would fold
|
|
200
|
+
# `Model.table_name == "…"` on correct code. `table_name_computed` is otherwise an OR. Name-keyed rows
|
|
201
|
+
# let the LAST declaration override an earlier same-name row, as {ModelIndex.merge_named_rows} does
|
|
202
|
+
# across an STI chain; plain lists union.
|
|
210
203
|
def merge_redeclarations(rows)
|
|
211
204
|
return rows if rows.length < 2
|
|
212
205
|
|
|
@@ -459,7 +452,6 @@ module Rigor
|
|
|
459
452
|
macro_methods: lookup_macro_methods(node.body)
|
|
460
453
|
})
|
|
461
454
|
|
|
462
|
-
# Recurse into the body in case nested classes exist.
|
|
463
455
|
walk_for_classes(node.body, [full_name], &) if node.body
|
|
464
456
|
end
|
|
465
457
|
|
|
@@ -869,8 +861,8 @@ module Rigor
|
|
|
869
861
|
|
|
870
862
|
# Walks the namespace body's TOP-LEVEL statements only — a decorator declared inside a nested
|
|
871
863
|
# `module`/`class` belongs to THAT namespace, not this one, and must not be attributed here.
|
|
872
|
-
# Returns
|
|
873
|
-
#
|
|
864
|
+
# Returns the decorators keyed by `:prefix` / `:suffix`, only for the names this body actually mentions.
|
|
865
|
+
# A later statement overrides an earlier one within the SAME body — real Ruby
|
|
874
866
|
# assignment order, not a guess: `mattr_accessor :table_name_prefix` (no `default:`, so `computed:
|
|
875
867
|
# true`) followed two lines down by a real `self.table_name_prefix = "blog_"` folds to the literal.
|
|
876
868
|
#
|
|
@@ -1234,8 +1226,8 @@ module Rigor
|
|
|
1234
1226
|
end
|
|
1235
1227
|
|
|
1236
1228
|
# Recognised single-instance and collection association DSL methods. The kind drives the eventual
|
|
1237
|
-
# return-type contribution: singular associations narrow to
|
|
1238
|
-
#
|
|
1229
|
+
# return-type contribution: singular associations narrow to the target or nil, plural ones to a
|
|
1230
|
+
# relation of the target.
|
|
1239
1231
|
#
|
|
1240
1232
|
# `composed_of` value-object aggregations and `delegated_type` roles are folded in here too — both
|
|
1241
1233
|
# accept the association name as a `where` / `find_by` query key, so omitting them turns every
|
|
@@ -1336,7 +1328,7 @@ module Rigor
|
|
|
1336
1328
|
association_option(args, "polymorphic") == true
|
|
1337
1329
|
|
|
1338
1330
|
# A polymorphic association has no single static target class — `target` is nil and the flow
|
|
1339
|
-
# contribution declines to narrow rather than inventing a wrong
|
|
1331
|
+
# contribution declines to narrow rather than inventing a wrong class-name target.
|
|
1340
1332
|
if polymorphic
|
|
1341
1333
|
target = nil
|
|
1342
1334
|
else
|
|
@@ -1460,8 +1452,7 @@ module Rigor
|
|
|
1460
1452
|
end
|
|
1461
1453
|
|
|
1462
1454
|
# `enum status: { active: 0, archived: 1 }` (Rails ≤6) and `enum :status, [:active, :archived]`
|
|
1463
|
-
# (Rails 7+).
|
|
1464
|
-
# guess.
|
|
1455
|
+
# (Rails 7+). Non-literal forms decline rather than guess.
|
|
1465
1456
|
def lookup_enums(body)
|
|
1466
1457
|
return {} if body.nil?
|
|
1467
1458
|
|
|
@@ -1516,7 +1507,7 @@ module Rigor
|
|
|
1516
1507
|
end
|
|
1517
1508
|
|
|
1518
1509
|
# `scope :active, -> { ... }`. Records the scope name only (the body is intentionally NOT
|
|
1519
|
-
# introspected — the caller contributes
|
|
1510
|
+
# introspected — the caller contributes a relation of the model based on the name alone
|
|
1520
1511
|
# via `class_scope_return_type`).
|
|
1521
1512
|
def lookup_scopes(body)
|
|
1522
1513
|
return [] if body.nil?
|
|
@@ -1585,8 +1576,7 @@ module Rigor
|
|
|
1585
1576
|
|
|
1586
1577
|
# `alias_attribute :new_name, :old_name`. Records the mapping so the analyzer accepts the alias
|
|
1587
1578
|
# as a query key — without it every `where(<alias>: ...)` / `find_by(<alias>: ...)` call surfaces
|
|
1588
|
-
# as a false `unknown-column`.
|
|
1589
|
-
# rather than guess.
|
|
1579
|
+
# as a false `unknown-column`. Non-Symbol-literal forms decline rather than guess.
|
|
1590
1580
|
def lookup_aliases(body)
|
|
1591
1581
|
return {} if body.nil?
|
|
1592
1582
|
|
|
@@ -1626,7 +1616,7 @@ module Rigor
|
|
|
1626
1616
|
# macro and `images`, `images=`, `images_attachments`, `images_blobs` for the collection one — the
|
|
1627
1617
|
# `has_one_attached` / `has_many_attached` generated-methods surface, none of it column-backed.
|
|
1628
1618
|
# This is name MEMBERSHIP only, and does not overlap `rigor-activestorage`, which contributes the
|
|
1629
|
-
# accessor's
|
|
1619
|
+
# accessor's type and publishes no fact: a consumer that reads
|
|
1630
1620
|
# `:model_index` to ask "does this model answer `avatar`?" gets nothing from that plugin, and gets
|
|
1631
1621
|
# nothing at all when the project does not load it.
|
|
1632
1622
|
ATTACHMENT_MACROS = {
|
|
@@ -29,14 +29,12 @@ module Rigor
|
|
|
29
29
|
# {Entry} resolves its table from the root model and inherits the chain's declared associations /
|
|
30
30
|
# enums / aliases / scopes / validations / callbacks / macro methods.
|
|
31
31
|
class ModelIndex
|
|
32
|
-
# `associations` is a frozen
|
|
33
|
-
# nullable: }`:
|
|
32
|
+
# `associations` is a frozen list of rows `{ name:, kind:, target:, nullable: }`:
|
|
34
33
|
#
|
|
35
|
-
# - `name` —
|
|
34
|
+
# - `name` — the association method name as the user invokes it (`"posts"`).
|
|
36
35
|
# - `kind` — `:singular` (`belongs_to` / `has_one`) or `:collection` (`has_many`).
|
|
37
|
-
# - `target` —
|
|
38
|
-
#
|
|
39
|
-
# - `nullable` — Boolean; whether a `:singular` accessor can return `nil`. `has_one` → `true`;
|
|
36
|
+
# - `target` — the target class name, from an explicit `class_name:` option or via {Inflector.classify}.
|
|
37
|
+
# - `nullable` — whether a `:singular` accessor can return `nil`. `has_one` → `true`;
|
|
40
38
|
# `belongs_to` → `false` (required by default since Rails 5) unless `optional: true`
|
|
41
39
|
# / `required: false`. Meaningless for `:collection` rows.
|
|
42
40
|
#
|
|
@@ -71,27 +69,23 @@ module Rigor
|
|
|
71
69
|
|
|
72
70
|
def association_names = associations.map { |a| a[:name] }
|
|
73
71
|
|
|
74
|
-
# `enums`
|
|
72
|
+
# `enums` maps a column name to its value names.
|
|
75
73
|
def enum?(name) = enums.key?(name.to_s)
|
|
76
74
|
def enum_values(name) = enums.fetch(name.to_s, [])
|
|
77
75
|
|
|
78
|
-
# `scopes` is `Array<scope_name>`.
|
|
79
76
|
def scope?(name) = scopes.include?(name.to_s)
|
|
80
77
|
|
|
81
|
-
# `validations`
|
|
82
|
-
# `validates_*_of :name, ...` shorthand families.
|
|
78
|
+
# `validations` covers both `validates :name, ...` and the `validates_*_of :name, ...` shorthand families.
|
|
83
79
|
def validation?(name) = validations.include?(name.to_s)
|
|
84
80
|
def validated_attributes = validations
|
|
85
81
|
|
|
86
|
-
# `callbacks` is `Array<{ name:, callback: }>`.
|
|
87
82
|
def callback_targets = callbacks.map { |c| c[:name] }
|
|
88
83
|
|
|
89
|
-
# `aliases`
|
|
90
|
-
# declarations.
|
|
84
|
+
# `aliases` maps an alias name to its target attribute, from `alias_attribute` declarations.
|
|
91
85
|
def alias?(name) = aliases.key?(name.to_s)
|
|
92
86
|
def resolve_alias(name) = aliases[name.to_s]
|
|
93
87
|
|
|
94
|
-
# `macro_methods`
|
|
88
|
+
# `macro_methods` lists the instance methods a declaration macro installs that
|
|
95
89
|
# none of the sets above already carries (#1049): `delegate`'s names, the Paperclip / Active
|
|
96
90
|
# Storage attachment readers, and an `enum`'s per-value predicates. Names only, with no type: a
|
|
97
91
|
# consumer asks "does this model answer `followers_count`?", which is what a fail-closed check
|
|
@@ -312,7 +306,7 @@ module Rigor
|
|
|
312
306
|
seen.values.map(&:freeze).freeze
|
|
313
307
|
end
|
|
314
308
|
|
|
315
|
-
#
|
|
309
|
+
# Enum value lists merged across the STI chain; a child redeclaration of the same
|
|
316
310
|
# enum column overrides the ancestor's value list.
|
|
317
311
|
def self.merge_enums(chain)
|
|
318
312
|
chain.each_with_object({}) do |row, acc|
|
|
@@ -320,7 +314,7 @@ module Rigor
|
|
|
320
314
|
end.freeze
|
|
321
315
|
end
|
|
322
316
|
|
|
323
|
-
#
|
|
317
|
+
# Alias map merged across the STI chain.
|
|
324
318
|
def self.merge_aliases(chain)
|
|
325
319
|
chain.each_with_object({}) do |row, acc|
|
|
326
320
|
(row[:aliases] || {}).each { |name, target| acc[name.to_s] = target.to_s }
|
|
@@ -108,9 +108,8 @@ module Rigor
|
|
|
108
108
|
false
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
# Walks the block body collecting `t.<method>(...)` calls.
|
|
112
|
-
# `if
|
|
113
|
-
# back silently.
|
|
111
|
+
# Walks the block body collecting `t.<method>(...)` calls. It descends through nested constructs
|
|
112
|
+
# (e.g. `if` branches) but does not recurse into a matched call.
|
|
114
113
|
def collect_column_calls(node, &)
|
|
115
114
|
return if node.nil?
|
|
116
115
|
|
|
@@ -59,8 +59,8 @@ 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",
|
|
@@ -553,8 +553,9 @@ module Rigor
|
|
|
553
553
|
end
|
|
554
554
|
|
|
555
555
|
# 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
|
|
556
|
+
# them for two or more ids, or with a block the model or `nil`; `find_by!` returns the model;
|
|
557
|
+
# `find_by` adds the `nil` arm; `where` / `all` / `order` / `limit` / `none` open a relation. The
|
|
558
|
+
# relation then carries its element type
|
|
558
559
|
# through any further chained query method via the bundled `ActiveRecord::Relation` RBS.
|
|
559
560
|
def finder_return_type(call_node, entry, scope)
|
|
560
561
|
case call_node.name
|
|
@@ -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
|
|
@@ -137,19 +137,14 @@ module Rigor
|
|
|
137
137
|
nil
|
|
138
138
|
end
|
|
139
139
|
|
|
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.
|
|
140
|
+
# Issue #1056 — "the attachment index did not load" is a fact about the run's INPUTS, not the file
|
|
141
|
+
# being analysed, so it is registered via {Plugin::Base#disclose_once} rather than returned from the
|
|
142
|
+
# per-file hook (which had no once-guard and re-emitted the list on every file).
|
|
145
143
|
#
|
|
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.
|
|
144
|
+
# The `key`s carry an ordinal prefix because the engine emits disclosures in KEY order (#1051), and
|
|
145
|
+
# a refused read must precede a discovery failure. `@load_errors` is keyed by the same string, which
|
|
146
|
+
# caps it: `#attachment_index` re-attempts the load per call site (#569). The key, not the message,
|
|
147
|
+
# is the identity, so a second refusal collapses into the first row.
|
|
153
148
|
def disclose_load_errors
|
|
154
149
|
@load_errors.each do |key, message|
|
|
155
150
|
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.",
|
|
@@ -190,11 +194,9 @@ module Rigor
|
|
|
190
194
|
argument = duration_arithmetic_argument(call_node)
|
|
191
195
|
next nil if argument.nil?
|
|
192
196
|
|
|
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.
|
|
197
|
+
# The ARGUMENT is checked before the receiver: it is the cheaper test on the common shapes (`i + 1`,
|
|
198
|
+
# `n * 2`), and this rule is consulted on every `+` / `-` / `*`. An argument that is no Duration
|
|
199
|
+
# operand (`"a" + b`, `list + other`) declines here and never types its receiver on this rule's behalf.
|
|
198
200
|
argument_kind = duration_operand_kind(scope&.type_of(argument))
|
|
199
201
|
next nil if argument_kind.nil?
|
|
200
202
|
|
|
@@ -204,6 +206,17 @@ module Rigor
|
|
|
204
206
|
duration_arithmetic_result(call_node.name, receiver_kind, argument_kind)
|
|
205
207
|
end
|
|
206
208
|
|
|
209
|
+
# #2433 — `symbolize_keys` / `to_options` keep a key without `to_sym`, so the key type is mapped per
|
|
210
|
+
# member; see {SymbolizeKeys}. The RBS rows answer `Hash[untyped, V]` wherever this declines.
|
|
211
|
+
dynamic_return methods: %i[symbolize_keys to_options] do |call_node, scope|
|
|
212
|
+
next nil unless call_node.is_a?(Prism::CallNode)
|
|
213
|
+
next nil unless call_node.receiver
|
|
214
|
+
next nil unless call_node.arguments.nil?
|
|
215
|
+
|
|
216
|
+
# A block is ignored by `symbolize_keys`, so it does not decline.
|
|
217
|
+
SymbolizeKeys.result(scope&.type_of(call_node.receiver), scope)
|
|
218
|
+
end
|
|
219
|
+
|
|
207
220
|
# ADR-88 WD1 — both tables are static and the RBS bundle ships with the gem; nothing here reads a
|
|
208
221
|
# project file, so a project enabling this plugin stays incremental-capable.
|
|
209
222
|
def incremental_state_fingerprint
|
|
@@ -1565,28 +1565,35 @@ class Hash[unchecked out K, unchecked out V]
|
|
|
1565
1565
|
# non-bang half is `%a{pure}`. `assert_valid_keys` raises rather than
|
|
1566
1566
|
# mutating (raising is not an effect label).
|
|
1567
1567
|
%a{pure}
|
|
1568
|
-
|
|
1568
|
+
# `key.to_sym rescue key` keeps a key without `to_sym`, so no one key type is right: `Hash[untyped, V]` here,
|
|
1569
|
+
# and the plugin's `dynamic_return` hook (#2433) maps each key member where it can.
|
|
1570
|
+
def symbolize_keys: () -> Hash[untyped, V]
|
|
1571
|
+
# `symbolize_keys!` keeps `-> self`: `key.to_sym rescue key` leaves a key without `to_sym` as it is, so
|
|
1572
|
+
# `Hash[Symbol, V]` is wrong for an Integer-keyed receiver (#2424 review). The other bang forms below
|
|
1573
|
+
# answer the re-keyed or re-valued hash.
|
|
1569
1574
|
def symbolize_keys!: () -> self
|
|
1570
1575
|
%a{pure}
|
|
1571
1576
|
def deep_symbolize_keys: () -> Hash[untyped, untyped]
|
|
1572
|
-
def deep_symbolize_keys!: () ->
|
|
1577
|
+
def deep_symbolize_keys!: () -> Hash[untyped, untyped]
|
|
1573
1578
|
%a{pure}
|
|
1574
1579
|
def stringify_keys: () -> Hash[String, V]
|
|
1575
|
-
def stringify_keys!: () ->
|
|
1580
|
+
def stringify_keys!: () -> Hash[String, V]
|
|
1576
1581
|
%a{pure}
|
|
1577
1582
|
def deep_stringify_keys: () -> Hash[untyped, untyped]
|
|
1578
|
-
def deep_stringify_keys!: () ->
|
|
1583
|
+
def deep_stringify_keys!: () -> Hash[untyped, untyped]
|
|
1579
1584
|
%a{pure}
|
|
1580
1585
|
def assert_valid_keys: (*K | Array[K]) -> self
|
|
1586
|
+
# The block's result is the new key at every level, so the keys are its type, not `K`: `{ a: 1 }
|
|
1587
|
+
# .deep_transform_keys(&:to_s)` has String keys. Nested values are re-keyed too, so they read `untyped`.
|
|
1581
1588
|
%a{pure}
|
|
1582
|
-
def deep_transform_keys: () { (K) ->
|
|
1583
|
-
def deep_transform_keys!: () { (K) ->
|
|
1589
|
+
def deep_transform_keys: [X] () { (K) -> X } -> Hash[X, untyped]
|
|
1590
|
+
def deep_transform_keys!: [X] () { (K) -> X } -> Hash[X, untyped]
|
|
1584
1591
|
%a{pure}
|
|
1585
1592
|
def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
|
|
1586
|
-
def deep_transform_values!: () { (V) -> untyped } ->
|
|
1593
|
+
def deep_transform_values!: () { (V) -> untyped } -> Hash[K, untyped]
|
|
1587
1594
|
# `alias_method`s of `symbolize_keys` / `symbolize_keys!`.
|
|
1588
1595
|
%a{pure}
|
|
1589
|
-
def to_options: () -> Hash[
|
|
1596
|
+
def to_options: () -> Hash[untyped, V]
|
|
1590
1597
|
def to_options!: () -> self
|
|
1591
1598
|
|
|
1592
1599
|
# `core_ext/hash/deep_dup` — allocates a deep copy, never touches the
|
|
@@ -1600,11 +1607,17 @@ class Hash[unchecked out K, unchecked out V]
|
|
|
1600
1607
|
# `core_ext/hash/deep_merge` — the block receives `(key, this_val,
|
|
1601
1608
|
# other_val)` and returns the merged value; neither `deep_merge` nor its
|
|
1602
1609
|
# block mutates either hash (containment covers the block itself).
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1610
|
+
# The result carries the argument's keys and values as well as the
|
|
1611
|
+
# receiver's, and a key both hold a Hash under merges into a third Hash
|
|
1612
|
+
# that is neither side's value type (`{ n: { x: 1 } }.deep_merge(n: { y:
|
|
1613
|
+
# "s" })[:n]` is `{ x: 1, y: "s" }`), so the values read `untyped`.
|
|
1614
|
+
# `(Hash[K, V]) -> Hash[K, V]` typed `h.deep_merge(b: "x")[:b]` as the
|
|
1615
|
+
# receiver's value type (#2424 review). The bang form answers the receiver.
|
|
1616
|
+
%a{pure}
|
|
1617
|
+
def deep_merge: [A, B] (Hash[A, B] other_hash) -> Hash[K | A, untyped]
|
|
1618
|
+
| [A, B] (Hash[A, B] other_hash) { (K | A, untyped, untyped) -> untyped } -> Hash[K | A, untyped]
|
|
1619
|
+
def deep_merge!: [A, B] (Hash[A, B] other_hash) -> Hash[K | A, untyped]
|
|
1620
|
+
| [A, B] (Hash[A, B] other_hash) { (K | A, untyped, untyped) -> untyped } -> Hash[K | A, untyped]
|
|
1608
1621
|
# `other.is_a?(Hash)` — the hook `ActiveSupport::DeepMergeable` asks before
|
|
1609
1622
|
# recursing into a value. `:nodoc:`, but public.
|
|
1610
1623
|
%a{pure}
|
|
@@ -37,9 +37,9 @@ module Rigor
|
|
|
37
37
|
@classes.empty?
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# The classes alba infers for the associations that name no resource, limited to
|
|
41
|
-
#
|
|
42
|
-
#
|
|
40
|
+
# The classes alba infers for the associations that name no resource, limited to those that exist in
|
|
41
|
+
# the project. The block receives each association name and returns alba's `classify` of it (the
|
|
42
|
+
# caller owns the inflector; ADR-39).
|
|
43
43
|
def inferred_roots(&classify)
|
|
44
44
|
roots = @associations.filter_map do |association|
|
|
45
45
|
next unless resource?(association.owner)
|
|
@@ -33,9 +33,11 @@ module Rigor
|
|
|
33
33
|
# - `:authenticatable` — always-included base module
|
|
34
34
|
#
|
|
35
35
|
# The substrate's pre-pass scanner consults each module's RBS via `Environment::RbsLoader#instance_definition`
|
|
36
|
-
# to enumerate method names
|
|
37
|
-
#
|
|
38
|
-
#
|
|
36
|
+
# to enumerate method names, so it names only what the project's RBS declares. The `devise` gem ships no
|
|
37
|
+
# RBS and Rigor bundles none for `Devise::Models::*`: a project supplies those signatures (`signature_paths:`
|
|
38
|
+
# or an `rbs collection`), and only then do `valid_password?`, `send_reset_password_instructions`, etc.
|
|
39
|
+
# resolve through the synthetic-method tier. A name the model defines itself, or a project module it
|
|
40
|
+
# includes or prepends defines, is left to the project's definition.
|
|
39
41
|
#
|
|
40
42
|
# ## Precision tier
|
|
41
43
|
#
|
|
@@ -212,9 +212,7 @@ module Rigor
|
|
|
212
212
|
# ... end` row's own recursive call (see {#each_block_type_info}). It caps the recursion at ONE
|
|
213
213
|
# level deep: with `nested: true`, {#walk_predicate_chain} declines a FURTHER `each do ... end`
|
|
214
214
|
# rather than recursing again (issue #137 deliberately does not model doubly-nested arrays of
|
|
215
|
-
# arrays).
|
|
216
|
-
# is the only caller that ever passes `nested: true}` — so the call graph has no cycle for the
|
|
217
|
-
# engine's return-type inference to approximate through.
|
|
215
|
+
# arrays). The recursion through {#each_block_type_info} is therefore bounded and terminates.
|
|
218
216
|
def collect_schema_shape(block_node, type_aliases, nested:)
|
|
219
217
|
required = {}
|
|
220
218
|
optional = {}
|
|
@@ -34,9 +34,9 @@ module Rigor
|
|
|
34
34
|
#
|
|
35
35
|
# The synthetic reader's return type is resolved via ADR-18's `returns_from_arg:` fact lookup: the
|
|
36
36
|
# call-site's second argument (`Types::String` etc.) is looked up through the `:dry_type_aliases`
|
|
37
|
-
# fact published by `rigor-dry-types`, yielding `
|
|
38
|
-
# misses (e.g. inline method-chain argument whose chain-head isn't currently extracted), the row
|
|
39
|
-
# falls back to
|
|
37
|
+
# fact published by `rigor-dry-types`, yielding the aliased class (`String`) for common cases. When the
|
|
38
|
+
# lookup misses (e.g. inline method-chain argument whose chain-head isn't currently extracted), the row
|
|
39
|
+
# falls back to the untyped floor silently.
|
|
40
40
|
#
|
|
41
41
|
# ## Scope (slice 2c minimum)
|
|
42
42
|
#
|
|
@@ -58,8 +58,8 @@ module Rigor
|
|
|
58
58
|
"by consuming `rigor-dry-types`'s `:dry_type_aliases` fact.",
|
|
59
59
|
# ADR-9 consumption — the precision-promotion path below uses `:dry_type_aliases` published by
|
|
60
60
|
# `rigor-dry-types`. The fact is optional: when the `rigor-dry-types` plugin isn't loaded, the
|
|
61
|
-
# `returns_from_arg:` lookup misses and the synthetic readers fall back to
|
|
62
|
-
# pre-ADR-18
|
|
61
|
+
# `returns_from_arg:` lookup misses and the synthetic readers fall back to the untyped floor (the
|
|
62
|
+
# pre-ADR-18 behaviour).
|
|
63
63
|
consumes: [{ plugin_id: "dry-types", name: :dry_type_aliases, optional: true }],
|
|
64
64
|
heredoc_templates: [
|
|
65
65
|
Rigor::Plugin::Macro::HeredocTemplate.new(
|
|
@@ -69,7 +69,7 @@ module Rigor
|
|
|
69
69
|
# ADR-18 — the synthetic reader's return type comes from the call site's second argument
|
|
70
70
|
# (`Types::String` etc.), resolved through the `:dry_type_aliases` fact. When the lookup
|
|
71
71
|
# misses (e.g. inline `attribute :tag, Types::String.constrained(...)`, whose receiver chain
|
|
72
|
-
# head isn't currently extracted), the row falls back to
|
|
72
|
+
# head isn't currently extracted), the row falls back to the untyped floor.
|
|
73
73
|
emit: [{
|
|
74
74
|
name: "\#{name}",
|
|
75
75
|
returns_from_arg: {
|