rigortype 0.4.6 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +77 -71
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +24 -31
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +31 -12
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +108 -102
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +12 -8
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +84 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +480 -67
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +41 -10
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +9 -13
- data/lib/rigor/plugin/registry.rb +3 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +15 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +365 -31
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +371 -58
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +3 -5
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +36 -3
|
@@ -4,6 +4,7 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../configuration"
|
|
6
6
|
require_relative "../environment"
|
|
7
|
+
require_relative "../run_scoped_slots"
|
|
7
8
|
require_relative "../project_environment"
|
|
8
9
|
require_relative "../scope"
|
|
9
10
|
require_relative "../reflection"
|
|
@@ -26,6 +27,7 @@ require_relative "declaration_equivalence"
|
|
|
26
27
|
require_relative "inline_declarations"
|
|
27
28
|
require_relative "layout_index"
|
|
28
29
|
require_relative "method_candidate"
|
|
30
|
+
require_relative "type_name_qualification"
|
|
29
31
|
require_relative "writer"
|
|
30
32
|
require_relative "../rooted_glob"
|
|
31
33
|
|
|
@@ -79,16 +81,20 @@ module Rigor
|
|
|
79
81
|
attr_reader :unrenderable, :unresolvable_superclasses
|
|
80
82
|
|
|
81
83
|
# @param paths — files / directories to scan.
|
|
82
|
-
# @param observations —
|
|
84
|
+
# @param observations —
|
|
83
85
|
# ADR-14 slice 3 — per-target-method arg-tuple observations
|
|
84
86
|
# produced by {ObservationCollector}. An empty Hash (the default)
|
|
85
87
|
# means "no observations available; emit `untyped` for every
|
|
86
88
|
# parameter position" per ADR-5 clause 2.
|
|
89
|
+
# @param target_writer — the {Writer} a `--write` or `--check` run hands the candidates to. When given, a
|
|
90
|
+
# class counts as written for a subclass only if that writer accepts its target file (#2461); nil (print,
|
|
91
|
+
# diff, JSON) counts every class with a candidate the writer is sure to write.
|
|
87
92
|
# @param overwrite — `--overwrite`: a member declared inline whose `sig/` copy disagrees is proposed as an
|
|
88
93
|
# `inline-overwrite` of the whole member instead of refused (ADR-112 WD4).
|
|
89
94
|
def initialize(configuration:, paths:, observations: {}, include_private: false, effect_annotator: nil,
|
|
90
|
-
overwrite: false)
|
|
95
|
+
overwrite: false, target_writer: nil)
|
|
91
96
|
@configuration = configuration
|
|
97
|
+
@target_writer = target_writer
|
|
92
98
|
@overwrite = overwrite
|
|
93
99
|
@paths = paths
|
|
94
100
|
@observations = normalize_observations(observations)
|
|
@@ -114,6 +120,36 @@ module Rigor
|
|
|
114
120
|
@unresolvable_superclasses = {}
|
|
115
121
|
# Issue #722 — whole-run: `{ class name => the lexical nesting its header is written in }`.
|
|
116
122
|
@superclass_nestings = {}
|
|
123
|
+
# Issue #2437 — whole-run: every class a walked file declares with a `class` keyword or a `Data.define` /
|
|
124
|
+
# `Struct.new` layout, so the report can tell a project superclass this run did not write from one nothing
|
|
125
|
+
# declares.
|
|
126
|
+
@source_classes = Set.new
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# How the report names an unresolvable superclass token ({#unresolvable_superclasses}). It is a project
|
|
130
|
+
# superclass when it names a class the walked source declares and this run wrote nothing for, whose own chain
|
|
131
|
+
# would close if the project's classes were written:
|
|
132
|
+
# each step a source class, ending at no superclass or at one the RBS environment knows (#2437). What is
|
|
133
|
+
# missing is then the project's own declaration, not a gem's RBS. `class ApplicationRecord <
|
|
134
|
+
# ActiveRecord::Base` with no RBS for the gem is not such a class: writing it would not help.
|
|
135
|
+
#
|
|
136
|
+
# Returns `[:project, token]` for such a class. Otherwise `[:external, root]`, where `root` is the first token
|
|
137
|
+
# on the chain that is not a source class: `User < ApplicationRecord` reports `ActiveRecord::Base`, the class
|
|
138
|
+
# whose RBS is missing, rather than the project class between them.
|
|
139
|
+
def unresolvable_superclass_report(token)
|
|
140
|
+
name = bare_superclass_name(token)
|
|
141
|
+
root = token
|
|
142
|
+
seen = {}
|
|
143
|
+
while @source_classes.include?(name) && !seen[name]
|
|
144
|
+
seen[name] = true
|
|
145
|
+
nesting, superclass = @superclass_nestings[name]
|
|
146
|
+
return [:project, token] if superclass.nil?
|
|
147
|
+
|
|
148
|
+
root = resolve_superclass_token(superclass, nesting, @source_classes, name)
|
|
149
|
+
name = bare_superclass_name(root)
|
|
150
|
+
return [:project, token] if Reflection.rbs_class_known?(name, environment: @environment)
|
|
151
|
+
end
|
|
152
|
+
[:external, root]
|
|
117
153
|
end
|
|
118
154
|
|
|
119
155
|
# Lifts legacy plain-`Array[Type]` observation entries into {ObservedCall} carriers. Specs from the
|
|
@@ -129,12 +165,28 @@ module Rigor
|
|
|
129
165
|
resolved = resolve_paths(@paths)
|
|
130
166
|
@resolved_paths = resolved
|
|
131
167
|
@environment = build_environment(resolved)
|
|
168
|
+
# ADR-84 WD2, as `Runner#run_analysis` mints it for `check` (#2300): one return-memo bucket for the whole
|
|
169
|
+
# run. Without it the memo keys on each file's merged `discovered_def_nodes`, a new table per file, so a
|
|
170
|
+
# callee reached from two files was re-inferred in each. A fresh token per run keeps a second run in one
|
|
171
|
+
# process from reading the first's entries, and the bucket is released with the other run-scoped slots
|
|
172
|
+
# when the run ends ({RunScopedSlots}), so a finished run holds none of it.
|
|
173
|
+
@run_generation = Object.new.freeze
|
|
174
|
+
# Both read the files as they are now, so a second `run` of one generator re-reads them.
|
|
175
|
+
@project_tables = nil
|
|
176
|
+
@file_base_scope = nil
|
|
177
|
+
# #2461 — which `sig/` targets the writer will write to, read from the files as they are now.
|
|
178
|
+
@writable_targets = {}
|
|
132
179
|
candidates = resolved.flat_map { |path| analyse_file(path, @environment) }
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
180
|
+
demoted = demote_overridden_base_methods(
|
|
181
|
+
demote_unresolvable_superclasses(
|
|
182
|
+
demote_generic_reopenings(demote_inline_generic_classes(resolve_superclass_spellings(candidates)))
|
|
136
183
|
)
|
|
137
184
|
)
|
|
185
|
+
# Again after the override demotion: a demoted method can leave its file with nothing to write, and a class
|
|
186
|
+
# shell riding on that file then declares nothing a subclass elsewhere could rest on.
|
|
187
|
+
annotate_effects(stamp_generic_reopenings(demote_unresolvable_superclasses(demoted)))
|
|
188
|
+
ensure
|
|
189
|
+
RunScopedSlots.release
|
|
138
190
|
end
|
|
139
191
|
|
|
140
192
|
private
|
|
@@ -209,10 +261,10 @@ module Rigor
|
|
|
209
261
|
decls.each do |decl|
|
|
210
262
|
next unless decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
|
|
211
263
|
|
|
212
|
-
|
|
213
|
-
return decl if
|
|
264
|
+
path = ::Rigor::Environment::RbsLoader.declared_class_path(decl, prefix)
|
|
265
|
+
return decl if path.join("::") == class_name
|
|
214
266
|
|
|
215
|
-
found = find_declaration(decl.members,
|
|
267
|
+
found = find_declaration(decl.members, path, class_name)
|
|
216
268
|
return found if found
|
|
217
269
|
end
|
|
218
270
|
nil
|
|
@@ -223,6 +275,88 @@ module Rigor
|
|
|
223
275
|
(1..segments.size).any? { |n| generic.key?(segments.first(n).join("::")) }
|
|
224
276
|
end
|
|
225
277
|
|
|
278
|
+
# Issue #2436 — a class the loaded RBS declares generic (`Array`, `Hash`, a gem's or the project's own) is
|
|
279
|
+
# reopened with that declaration's type parameters copied onto the header ({#stamp_generic_reopenings}),
|
|
280
|
+
# which is exact only while every parameter is plain. A bound or a default is a type spelled relative to the
|
|
281
|
+
# declaring file's namespace, and copying it under another nesting can resolve it to a different class, so
|
|
282
|
+
# rbs would reject the header as it rejects a parameterless one (`GenericParameterMismatchError`): the class,
|
|
283
|
+
# and every class whose signature mentions it, fails its definition build. A method of such a class, or of
|
|
284
|
+
# one nested in it, is skipped instead — unless `sig/` already declares the class, whose own header the
|
|
285
|
+
# writer then writes into ({#demote_inline_generic_classes}' rule).
|
|
286
|
+
def demote_generic_reopenings(candidates)
|
|
287
|
+
loader = @environment&.rbs_loader
|
|
288
|
+
return candidates if loader.nil?
|
|
289
|
+
|
|
290
|
+
layout = nil
|
|
291
|
+
candidates.map do |candidate|
|
|
292
|
+
next candidate unless Classification::EMITTABLE.include?(candidate.classification)
|
|
293
|
+
next candidate unless namespace_prefixes(candidate.class_name).any? { |name| bounded_generic?(loader, name) }
|
|
294
|
+
|
|
295
|
+
layout ||= LayoutIndex.new(signature_paths: @configuration.signature_paths)
|
|
296
|
+
next candidate if layout.file_for(candidate.class_name)
|
|
297
|
+
|
|
298
|
+
demoted_candidate(candidate, :generic_reopening)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# The other half of {#demote_generic_reopenings}: every emittable candidate carries the header spelling of
|
|
303
|
+
# each generic class the run may open a header for — its own class, the classes it is nested in, and the
|
|
304
|
+
# file's class shells and theirs — so the writer and `--print` spell the same header. Method bodies keep
|
|
305
|
+
# `untyped` for element types; nothing here names `E` in a member.
|
|
306
|
+
#
|
|
307
|
+
# Issue #2580 — a skipped candidate is stamped too, and its class counts towards the map: the writer
|
|
308
|
+
# repairs a parameterless header an earlier run wrote into the file a candidate routes to, and a file whose
|
|
309
|
+
# every proposal is already declared (or untyped) would otherwise keep a header rbs rejects.
|
|
310
|
+
def stamp_generic_reopenings(candidates)
|
|
311
|
+
loader = @environment&.rbs_loader
|
|
312
|
+
return candidates if loader.nil?
|
|
313
|
+
|
|
314
|
+
spelled = header_type_params(candidates.flat_map { |c| [c.class_name, *c.class_shells] }.compact)
|
|
315
|
+
return candidates if spelled.empty?
|
|
316
|
+
|
|
317
|
+
candidates.map { |candidate| candidate.with_class_type_params(spelled) }
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# #2603 — the header spelling {#header_type_params} gives `class_name` alone, or nil when the loaded RBS does
|
|
321
|
+
# not declare it generic with plain parameters only. The writer asks it for a bare header in a `sig/` file no
|
|
322
|
+
# candidate of the run routes to. Nil before {#run} has built the environment.
|
|
323
|
+
def generic_header_spelling(class_name)
|
|
324
|
+
loader = @environment&.rbs_loader
|
|
325
|
+
return nil if loader.nil?
|
|
326
|
+
|
|
327
|
+
params = loader.class_declaration_type_params(class_name)
|
|
328
|
+
"[#{params.join(', ')}]" if !params.empty? && params.all? { |p| plain_type_param?(p) }
|
|
329
|
+
end
|
|
330
|
+
public :generic_header_spelling
|
|
331
|
+
|
|
332
|
+
# `{ class name => "[unchecked out E]" }` for every name in `names`, or a namespace it is nested in, whose
|
|
333
|
+
# loaded declaration is generic with plain parameters only.
|
|
334
|
+
def header_type_params(names)
|
|
335
|
+
names.uniq.flat_map { |name| namespace_prefixes(name) }.uniq.each_with_object({}) do |name, acc|
|
|
336
|
+
spelling = generic_header_spelling(name)
|
|
337
|
+
acc[name] = spelling if spelling
|
|
338
|
+
end.freeze
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def namespace_prefixes(class_name)
|
|
342
|
+
return [] if class_name.nil?
|
|
343
|
+
|
|
344
|
+
segments = class_name.split("::")
|
|
345
|
+
(1..segments.size).map { |n| segments.first(n).join("::") }
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def bounded_generic?(loader, name)
|
|
349
|
+
params = loader.class_declaration_type_params(name)
|
|
350
|
+
!params.empty? && !params.all? { |param| plain_type_param?(param) }
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# A name, a variance and `unchecked`, and nothing that spells a type: no upper or lower bound, no default.
|
|
354
|
+
def plain_type_param?(param)
|
|
355
|
+
%i[upper_bound lower_bound default_type].none? do |reader|
|
|
356
|
+
param.respond_to?(reader) && param.public_send(reader)
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
226
360
|
# Issue #744 — a base class's method is NOT emitted when a project subclass overrides it and the
|
|
227
361
|
# override is not emitted itself.
|
|
228
362
|
#
|
|
@@ -241,7 +375,7 @@ module Rigor
|
|
|
241
375
|
# (#744 half 2) and is not decided here: this pass only stops sig-gen manufacturing the conflict.
|
|
242
376
|
def demote_overridden_base_methods(candidates)
|
|
243
377
|
superclasses = candidates.each_with_object({}) do |candidate, acc|
|
|
244
|
-
(candidate.class_superclasses || {}).each { |name, sup| acc[name] = sup
|
|
378
|
+
(candidate.class_superclasses || {}).each { |name, sup| acc[name] = bare_superclass_name(sup) }
|
|
245
379
|
end
|
|
246
380
|
return candidates if superclasses.empty?
|
|
247
381
|
|
|
@@ -305,6 +439,10 @@ module Rigor
|
|
|
305
439
|
def resolve_superclass_spellings(candidates)
|
|
306
440
|
return candidates if @superclass_nestings.empty?
|
|
307
441
|
|
|
442
|
+
# Every shell counts here, written or not: the question is which class Ruby means by the token, and the
|
|
443
|
+
# source declares `Lib::Error` whether or not its file is written. Whether the resolved superclass reaches
|
|
444
|
+
# disk is {#demote_unresolvable_superclasses}'s question. Counting only written shells would leave `Error`
|
|
445
|
+
# unresolved, and a top-level `Error` some signature declares would then take it (#2454).
|
|
308
446
|
known = candidates.each_with_object(Set.new) do |candidate, acc|
|
|
309
447
|
acc << candidate.class_name if candidate.class_name
|
|
310
448
|
acc.merge(candidate.class_shells || [])
|
|
@@ -324,20 +462,32 @@ module Rigor
|
|
|
324
462
|
@superclass_nestings.each_with_object({}) do |(full, (nesting, token)), acc|
|
|
325
463
|
next if token.nil? || nesting.empty?
|
|
326
464
|
|
|
327
|
-
resolved = resolve_superclass_token(token, nesting, known)
|
|
465
|
+
resolved = resolve_superclass_token(token, nesting, known, full)
|
|
328
466
|
acc[full] = resolved if resolved && resolved != token
|
|
329
467
|
end
|
|
330
468
|
end
|
|
331
469
|
|
|
332
470
|
# The first candidate spelling that names a class this run declares or the RBS environment knows, or
|
|
333
|
-
#
|
|
334
|
-
|
|
471
|
+
# the token itself when none does — an unresolvable superclass is #735's business, not this pass's.
|
|
472
|
+
#
|
|
473
|
+
# `full` is the class whose header carries the token. Ruby evaluates the superclass expression before the
|
|
474
|
+
# new constant exists, so the innermost candidate `full` itself (`module A; module C; class B < B`, where
|
|
475
|
+
# `A::C::B` is the class being opened) is not yet a candidate: the lookup falls through to `A::B` (#2579).
|
|
476
|
+
def resolve_superclass_token(token, nesting, known, full)
|
|
477
|
+
# A rooted token (`::Hash`) already names one class; the nesting must not capture it (#2459).
|
|
478
|
+
return token if token.start_with?("::")
|
|
479
|
+
|
|
335
480
|
base = token.sub(/\[.*\]\z/, "")
|
|
336
|
-
args = token[base.length..] || ""
|
|
337
481
|
nesting.length.downto(1) do |i|
|
|
338
482
|
candidate = "#{nesting[0, i].join('::')}::#{base}"
|
|
339
|
-
|
|
340
|
-
|
|
483
|
+
next if candidate == full
|
|
484
|
+
|
|
485
|
+
# The arguments {#record_superclass} computed belong to the top-level class of the bare name, so they are
|
|
486
|
+
# computed again for the class the token resolves to: `Hash` inside a `module Foo` declaring a plain
|
|
487
|
+
# `Foo::Hash` takes none (#2459).
|
|
488
|
+
return generic_superclass_spelling(candidate) if known.include?(candidate) ||
|
|
489
|
+
Reflection.rbs_class_known?(candidate,
|
|
490
|
+
environment: @environment)
|
|
341
491
|
end
|
|
342
492
|
token
|
|
343
493
|
end
|
|
@@ -370,28 +520,77 @@ module Rigor
|
|
|
370
520
|
# ran — source discovery and the plugins still type it. Emitting the class with the superclass merely
|
|
371
521
|
# OMITTED would be the harmful middle: the class becomes RBS-known with only the emitted methods, so
|
|
372
522
|
# every inherited call fires `call.undefined-method`.
|
|
523
|
+
#
|
|
524
|
+
# Demoting a class can leave a file with nothing to write, which takes the class shells riding on it off the
|
|
525
|
+
# emitted set, so the pass repeats until no further class becomes unresolvable. The emitted set only shrinks,
|
|
526
|
+
# so the unresolvable set only grows and the loop ends.
|
|
373
527
|
def demote_unresolvable_superclasses(candidates)
|
|
374
528
|
superclasses = candidates.each_with_object({}) do |candidate, acc|
|
|
375
529
|
(candidate.class_superclasses || {}).each { |name, sup| acc[name] = sup }
|
|
376
530
|
end
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
531
|
+
current = candidates
|
|
532
|
+
unresolvable = {}
|
|
533
|
+
loop do
|
|
534
|
+
found = unresolvable_superclass_roots(superclasses, declared_by_run(current))
|
|
535
|
+
return current if found.size == unresolvable.size
|
|
536
|
+
|
|
537
|
+
unresolvable = found
|
|
538
|
+
current = candidates.map do |candidate|
|
|
539
|
+
owner = unresolvable_for(candidate.class_name, unresolvable)
|
|
540
|
+
owner ? record_unresolvable_superclass(candidate, owner) : candidate
|
|
541
|
+
end
|
|
382
542
|
end
|
|
383
|
-
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def unresolvable_superclass_roots(superclasses, emitted)
|
|
546
|
+
superclasses.each_with_object({}) do |(name, _sup), acc|
|
|
384
547
|
root = unresolved_superclass_root(name, superclasses, emitted)
|
|
385
548
|
acc[name] = root if root
|
|
386
549
|
end
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
candidates.map do |candidate|
|
|
390
|
-
owner = unresolvable_for(candidate.class_name, unresolvable)
|
|
391
|
-
next candidate if owner.nil?
|
|
550
|
+
end
|
|
392
551
|
|
|
393
|
-
|
|
552
|
+
# Everything this run puts on disk, superclass or not: a chain that reaches `class Record` — emitted here, with
|
|
553
|
+
# no superclass of its own — terminates in a class the next run WILL know. Only what the writer is sure to
|
|
554
|
+
# write counts, because a subclass written against a class nothing declares fails its build
|
|
555
|
+
# (`RBS::NoSuperclassFoundError`) on the next run:
|
|
556
|
+
#
|
|
557
|
+
# - a class with a candidate the writer is sure to write ({#written?}). A class whose every method is skipped
|
|
558
|
+
# is not written, so it does not count (#2437); a class shell on it would close it instead (#2438);
|
|
559
|
+
# - a class shell on a file with such a candidate: a shell on a file whose every method is skipped, already
|
|
560
|
+
# declared by `sig/`, or a tighter return the writer will not apply never reaches disk (#2454);
|
|
561
|
+
# - the wrapper namespaces of either, which the writer renders around them with their recorded superclasses.
|
|
562
|
+
#
|
|
563
|
+
# Counting too few only demotes a subclass, the output before #2454 (ADR-5).
|
|
564
|
+
def declared_by_run(candidates)
|
|
565
|
+
written = candidates.select { |candidate| written?(candidate) }
|
|
566
|
+
written_paths = written.to_set(&:path)
|
|
567
|
+
declared = Set.new
|
|
568
|
+
written.each { |candidate| declared << candidate.class_name if candidate.class_name }
|
|
569
|
+
candidates.each do |candidate|
|
|
570
|
+
declared.merge(candidate.class_shells || []) if written_paths.include?(candidate.path)
|
|
394
571
|
end
|
|
572
|
+
declared.flat_map { |name| wrapper_names(name) }.to_set | declared
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# `A::B::C` is written inside `A` and `A::B`.
|
|
576
|
+
def wrapper_names(name)
|
|
577
|
+
segments = name.split("::")
|
|
578
|
+
(1...segments.size).map { |n| segments.first(n).join("::") }
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# Whether the writer will put `candidate` on disk: a candidate it is sure to write ({Writer.writes?}), and,
|
|
582
|
+
# when the run writes or compares files (`--write`, `--check`), aimed at a target that writer will write to
|
|
583
|
+
# (#2461). Print, diff and JSON runs have no writer: where a file would go depends on the working directory
|
|
584
|
+
# the writer takes its sig root from, and their output must not.
|
|
585
|
+
def written?(candidate)
|
|
586
|
+
return false unless Writer.writes?(candidate)
|
|
587
|
+
return true if @target_writer.nil?
|
|
588
|
+
|
|
589
|
+
target = @target_writer.path_mapper.target_for(candidate.path, class_name: candidate.class_name,
|
|
590
|
+
method_name: candidate.method_name,
|
|
591
|
+
kind: candidate.rbs_kind)
|
|
592
|
+
@writable_targets[target] = @target_writer.accepts_target?(target) unless @writable_targets.key?(target)
|
|
593
|
+
@writable_targets[target]
|
|
395
594
|
end
|
|
396
595
|
|
|
397
596
|
# A nested class or module goes with its wrapper. `WikiPage::Webhookable`'s declaration is emitted
|
|
@@ -417,9 +616,9 @@ module Rigor
|
|
|
417
616
|
return superclass if seen[name]
|
|
418
617
|
|
|
419
618
|
seen[name] = true
|
|
420
|
-
# The recorded spelling may carry the type arguments {#generic_superclass_spelling} added
|
|
421
|
-
# resolvability question is about the class, so it is asked of the bare name.
|
|
422
|
-
base = superclass
|
|
619
|
+
# The recorded spelling may carry the type arguments {#generic_superclass_spelling} added, and a rooted
|
|
620
|
+
# token its `::` (#2459); the resolvability question is about the class, so it is asked of the bare name.
|
|
621
|
+
base = bare_superclass_name(superclass)
|
|
423
622
|
return nil if Reflection.rbs_class_known?(base, environment: @environment)
|
|
424
623
|
return superclass unless emitted.include?(base)
|
|
425
624
|
|
|
@@ -460,15 +659,27 @@ module Rigor
|
|
|
460
659
|
end.uniq
|
|
461
660
|
end
|
|
462
661
|
|
|
463
|
-
# #1154 — the project's discovery tables,
|
|
464
|
-
#
|
|
465
|
-
#
|
|
466
|
-
#
|
|
467
|
-
|
|
468
|
-
|
|
662
|
+
# #1154 / #2300 — the project's discovery tables, built once per run over the configured project paths as well
|
|
663
|
+
# as the files sig-gen reads, so a run over one file reads what a `check` of it reads. Every file is indexed
|
|
664
|
+
# over them ({#file_base_scope}): a helper, an ancestor or a constant another file defines resolves as it does
|
|
665
|
+
# under `check` instead of reading ABSENT. The declared-parameter ivar credit reads them whole
|
|
666
|
+
# ({#ivar_writer_scope}).
|
|
667
|
+
def project_tables
|
|
668
|
+
@project_tables ||= Inference::ProjectSeedTables.for_paths(
|
|
469
669
|
((@resolved_paths || []) + resolve_paths(Array(@configuration.paths))).uniq { |path| File.expand_path(path) }
|
|
470
670
|
)
|
|
471
|
-
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# The member layouts stay per-file. sig-gen writes a value class for every `Data.define` / `Struct.new` layout
|
|
674
|
+
# the indexed scope carries ({#collect_meta_layouts}), so a seeded layout would re-emit every value class of the
|
|
675
|
+
# project in every file.
|
|
676
|
+
PER_FILE_TABLES = %i[data_member_layouts struct_member_layouts].freeze
|
|
677
|
+
private_constant :PER_FILE_TABLES
|
|
678
|
+
|
|
679
|
+
# #1154 — the declared-parameter ivar credit must see the ivar writers in other files (an ancestor, another
|
|
680
|
+
# object's `instance_variable_set`, a reopening) as `rigor check` does.
|
|
681
|
+
def ivar_writer_scope(base_scope)
|
|
682
|
+
base_scope.with_discovery(base_scope.discovery.with(**project_tables))
|
|
472
683
|
end
|
|
473
684
|
|
|
474
685
|
# #1406 — sig-gen keeps the census's own answer: a declared ivar type read back would echo the declaration.
|
|
@@ -478,12 +689,24 @@ module Rigor
|
|
|
478
689
|
)
|
|
479
690
|
end
|
|
480
691
|
|
|
692
|
+
# The scope each file is indexed under: the project's tables less the per-file ones ({#project_tables}), and the
|
|
693
|
+
# run's return-memo token (see {#run}), which {#ivar_writer_scope} inherits, so every reading of a callee shares
|
|
694
|
+
# one memo bucket. Built once per run.
|
|
695
|
+
def file_base_scope(environment)
|
|
696
|
+
@file_base_scope ||= begin
|
|
697
|
+
base = Scope.empty(environment: environment)
|
|
698
|
+
tables = project_tables.except(*PER_FILE_TABLES)
|
|
699
|
+
tables = tables.merge(run_generation: @run_generation) if @run_generation
|
|
700
|
+
base.with_discovery(base.discovery.with(**tables))
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
481
704
|
def analyse_file(path, environment)
|
|
482
705
|
source = File.read(path)
|
|
483
706
|
parse_result = Prism.parse(source, filepath: path, version: @configuration.target_ruby)
|
|
484
707
|
return [] if parse_result.errors.any?
|
|
485
708
|
|
|
486
|
-
scope_index = index_file(parse_result.value,
|
|
709
|
+
scope_index = index_file(parse_result.value, file_base_scope(environment))
|
|
487
710
|
|
|
488
711
|
reset_file_maps
|
|
489
712
|
@meta_layouts = collect_meta_layouts(scope_index)
|
|
@@ -518,11 +741,12 @@ module Rigor
|
|
|
518
741
|
#
|
|
519
742
|
# Only a definer whose statement certainly runs can be last: a direct statement of a `class`, `module` or
|
|
520
743
|
# `class << self` body, of a `Data.define` / `Struct.new` block or of the file ({#walk_statements} records
|
|
521
|
-
# them)
|
|
522
|
-
#
|
|
744
|
+
# them), or a `def` passed to `private`, `public` or `protected` in such a statement ({#walk_statements}, #2254).
|
|
745
|
+
# One under a condition, in a block or a lambda, or after an uncertain `module_function` reset may not run, as
|
|
746
|
+
# far as this reading tells. A slot such a definer could
|
|
523
747
|
# still take over from the last certain one is contested, and nothing is written for it.
|
|
524
748
|
#
|
|
525
|
-
# @return
|
|
749
|
+
# @return the definers of each slot as `[offset, certain]` pairs, keyed by `[class_name, kind, method_name]`.
|
|
526
750
|
MEMBER_OFFSET = -1
|
|
527
751
|
private_constant :MEMBER_OFFSET
|
|
528
752
|
|
|
@@ -677,6 +901,9 @@ module Rigor
|
|
|
677
901
|
# the `self` one returns above): it defines a singleton method on `obj`, not a private `Object` method.
|
|
678
902
|
def walk_def_children(node, prefix, in_singleton_class, module_function_active, out)
|
|
679
903
|
return if refine_block_call?(node)
|
|
904
|
+
# #2259 — a `def` in `class_methods do … end` (ActiveSupport::Concern) defines a method of the including class's
|
|
905
|
+
# singleton, not of this module. Omitted, not written class-side: no precedent places it on the singleton.
|
|
906
|
+
return if class_methods_block?(node)
|
|
680
907
|
|
|
681
908
|
in_block = node.is_a?(Prism::BlockNode) || node.is_a?(Prism::LambdaNode) ||
|
|
682
909
|
node.is_a?(Prism::SingletonClassNode)
|
|
@@ -706,6 +933,7 @@ module Rigor
|
|
|
706
933
|
|
|
707
934
|
full = child_prefix.join("::")
|
|
708
935
|
@namespace_kinds[full] = node.is_a?(Prism::ClassNode) ? :class : :module
|
|
936
|
+
@source_classes << full if node.is_a?(Prism::ClassNode)
|
|
709
937
|
record_superclass(node, full, prefix)
|
|
710
938
|
walk_namespace_body(node, child_prefix, out)
|
|
711
939
|
true
|
|
@@ -725,6 +953,17 @@ module Rigor
|
|
|
725
953
|
!@in_effect_refinements.class_body_refine?(node)
|
|
726
954
|
end
|
|
727
955
|
|
|
956
|
+
# A block call whose members are not this class body's: a `refine` block (#1763), and a `class_methods` block
|
|
957
|
+
# (#2259), whose members belong to the including class's singleton.
|
|
958
|
+
def skipped_block_call?(node)
|
|
959
|
+
refine_block_call?(node) || class_methods_block?(node)
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
def class_methods_block?(node)
|
|
963
|
+
node.is_a?(Prism::CallNode) && node.receiver.nil? && node.name == :class_methods &&
|
|
964
|
+
node.block.is_a?(Prism::BlockNode)
|
|
965
|
+
end
|
|
966
|
+
|
|
728
967
|
def declined_self_header?(node)
|
|
729
968
|
path = node.constant_path
|
|
730
969
|
path.is_a?(Prism::ConstantPathNode) && path.parent.is_a?(Prism::SelfNode) &&
|
|
@@ -750,10 +989,43 @@ module Rigor
|
|
|
750
989
|
# the name it resolves to may be a class another file declares. Resolved once every file has been
|
|
751
990
|
# walked, in {#resolve_superclass_spellings}.
|
|
752
991
|
spelling = generic_superclass_spelling(superclass)
|
|
992
|
+
# Issue #2579 — a token that names the class being opened (`class A::B < A::B`, or a top-level `class B < B`
|
|
993
|
+
# with nothing else of that name) would write a self-superclass, which RBS rejects as a recursive ancestor.
|
|
994
|
+
# No superclass is safer than that, so the declaration is written without one.
|
|
995
|
+
return if bare_superclass_name(spelling) == full
|
|
996
|
+
|
|
753
997
|
# The token rides along because `@class_superclasses` is per-FILE scratch, reset before the next
|
|
754
998
|
# file, while this table is whole-run.
|
|
755
999
|
@superclass_nestings[full] = [prefix.dup, spelling]
|
|
756
1000
|
@class_superclasses[full] = spelling
|
|
1001
|
+
register_core_subclass_shell(full, prefix, superclass)
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
# Issue #2224 — a subclass of a core or stdlib class got a declaration only when it had a method of its own to
|
|
1005
|
+
# emit, so after `class MyHash < Hash; end` a method returning one was written `-> MyHash` against a class no
|
|
1006
|
+
# signature declared, and `rbs validate` failed with `NoTypeFoundError`. As a class shell it is declared
|
|
1007
|
+
# `class MyHash < ::Hash[untyped, untyped]` beside the file's other declarations. A class the RBS environment
|
|
1008
|
+
# already declares (the project's `sig/`, an earlier run) is left to that declaration, and a superclass the
|
|
1009
|
+
# header's nesting shadows is not core's: `Hash` inside a `module Foo` that declares `Foo::Hash` is the project's.
|
|
1010
|
+
# A rooted `::Hash` names core's class whatever the nesting declares, so the shadow cannot capture it (#2459).
|
|
1011
|
+
def register_core_subclass_shell(full, prefix, superclass)
|
|
1012
|
+
return if class_known?(full)
|
|
1013
|
+
|
|
1014
|
+
bare = bare_superclass_name(superclass)
|
|
1015
|
+
return unless @environment&.rbs_loader&.core_or_stdlib_class?(bare)
|
|
1016
|
+
|
|
1017
|
+
unless superclass.start_with?("::")
|
|
1018
|
+
namespaces = TypeNameQualification.enclosing_namespaces(prefix.join("::"))
|
|
1019
|
+
return if TypeNameQualification.shadowed?(bare.split("::").first, namespaces, method(:declared_constant?))
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
@class_shells << full
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
# The class a recorded superclass token names, as the fully qualified key every class-name set and the RBS
|
|
1026
|
+
# loader use: the type arguments {#generic_superclass_spelling} added and a rooted token's `::` both dropped.
|
|
1027
|
+
def bare_superclass_name(token)
|
|
1028
|
+
token.sub(/\[.*\]\z/, "").delete_prefix("::")
|
|
757
1029
|
end
|
|
758
1030
|
|
|
759
1031
|
# Issue #735 — a GENERIC superclass has to be written with its arguments. `class Entries < Array`
|
|
@@ -766,7 +1038,7 @@ module Rigor
|
|
|
766
1038
|
loader = @environment&.rbs_loader
|
|
767
1039
|
return superclass if loader.nil?
|
|
768
1040
|
|
|
769
|
-
arity = loader.class_type_param_names(superclass).size
|
|
1041
|
+
arity = loader.class_type_param_names(bare_superclass_name(superclass)).size
|
|
770
1042
|
return superclass if arity.zero?
|
|
771
1043
|
|
|
772
1044
|
"#{superclass}[#{(['untyped'] * arity).join(', ')}]"
|
|
@@ -814,6 +1086,7 @@ module Rigor
|
|
|
814
1086
|
@meta_layouts.each do |class_name, layout|
|
|
815
1087
|
@namespace_kinds[class_name] = :class
|
|
816
1088
|
@class_shells << class_name
|
|
1089
|
+
@source_classes << class_name
|
|
817
1090
|
@class_superclasses[class_name] = MetaClassShape::SUPERCLASSES.fetch(layout.kind)
|
|
818
1091
|
end
|
|
819
1092
|
end
|
|
@@ -862,6 +1135,8 @@ module Rigor
|
|
|
862
1135
|
@certain_definers << stmt
|
|
863
1136
|
# A `begin … end` statement's body (and `ensure`) runs whenever the statement does.
|
|
864
1137
|
mark_certain_body(stmt) if stmt.is_a?(Prism::BeginNode)
|
|
1138
|
+
# A `def` passed to a visibility call (`private def x`, #2254) runs with its statement.
|
|
1139
|
+
@certain_definers.merge(Inference::ModuleFunctionState.visibility_call_defs(stmt))
|
|
865
1140
|
end
|
|
866
1141
|
walk_defs(stmt, prefix, in_singleton_class, active, out)
|
|
867
1142
|
end
|
|
@@ -959,8 +1234,8 @@ module Rigor
|
|
|
959
1234
|
# `Object#initialize: () -> void`. The mismatch surfaces a `Ruby::MethodParameterMismatch` warning even
|
|
960
1235
|
# when `rigor check` itself is clean.
|
|
961
1236
|
#
|
|
962
|
-
#
|
|
963
|
-
#
|
|
1237
|
+
# A default constructor is skipped (`classify_def` returns nil for it); a non-trivial one is not, and
|
|
1238
|
+
# {#initialize_stub_candidate} emits a permissive `(<param shape>) -> void` stub matching the runtime
|
|
964
1239
|
# parameter list.
|
|
965
1240
|
def initialize_excludes?(def_node, kind)
|
|
966
1241
|
return false unless kind == :instance
|
|
@@ -1088,25 +1363,36 @@ module Rigor
|
|
|
1088
1363
|
# return position it is not parenthesised.
|
|
1089
1364
|
def positional_type(observations, index, prefix, owner = nil)
|
|
1090
1365
|
types = observations.filter_map { |obs| obs.positional[index] }
|
|
1091
|
-
"#{prefix}#{union_erase(types, owner: owner)}"
|
|
1366
|
+
"#{prefix}#{union_erase(widen_observed_params(types), owner: owner)}"
|
|
1092
1367
|
end
|
|
1093
1368
|
|
|
1094
1369
|
def render_keyword_param(keyword, observations, owner = nil)
|
|
1095
1370
|
optional_marker = keyword.is_a?(Prism::OptionalKeywordParameterNode) ? "?" : ""
|
|
1096
1371
|
types = observations.filter_map { |obs| obs.keyword[keyword.name] }
|
|
1097
|
-
rendered = types.empty? ? "untyped" : paren_wrap_union(union_erase(types, owner: owner))
|
|
1372
|
+
rendered = types.empty? ? "untyped" : paren_wrap_union(union_erase(widen_observed_params(types), owner: owner))
|
|
1098
1373
|
"#{optional_marker}#{keyword.name}: #{rendered}"
|
|
1099
1374
|
end
|
|
1100
1375
|
|
|
1376
|
+
# #2254 — an observed argument is one call's value, so a literal (`"hello"`) proposes its class (`String`), as
|
|
1377
|
+
# {#widen_value_literals} does for a return. Widening always: a parameter the body only compares against a
|
|
1378
|
+
# literal (a discriminant) is widened too, which loses precision and never adds a false positive.
|
|
1379
|
+
def widen_observed_params(types)
|
|
1380
|
+
types.map { |type| widen_value_literals(type) }
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1383
|
+
# Issue #2459 — a ROOTED path (`::Hash`, `::A::B`) keeps its leading `::`: it names exactly one class, and the
|
|
1384
|
+
# passes after {#record_superclass} must not re-resolve it against the header's nesting, where a project
|
|
1385
|
+
# `Foo::Hash` would capture it. Every pass that compares the token against class names strips the anchor.
|
|
1101
1386
|
def qualified_constant_path(constant_path)
|
|
1102
1387
|
case constant_path
|
|
1103
1388
|
when Prism::ConstantReadNode
|
|
1104
1389
|
constant_path.name.to_s
|
|
1105
1390
|
when Prism::ConstantPathNode
|
|
1106
|
-
parent = qualified_constant_path(constant_path.parent) if constant_path.parent
|
|
1107
1391
|
name = constant_path.name&.to_s
|
|
1108
1392
|
return nil if name.nil?
|
|
1393
|
+
return "::#{name}" if constant_path.parent.nil?
|
|
1109
1394
|
|
|
1395
|
+
parent = qualified_constant_path(constant_path.parent)
|
|
1110
1396
|
parent ? "#{parent}::#{name}" : name
|
|
1111
1397
|
end
|
|
1112
1398
|
end
|
|
@@ -1520,16 +1806,16 @@ module Rigor
|
|
|
1520
1806
|
# about the return. The class's own declaration is kept whole instead: its visibility, the overload's
|
|
1521
1807
|
# annotations, type parameters, parameters and block, with only the return replaced.
|
|
1522
1808
|
#
|
|
1523
|
-
#
|
|
1809
|
+
# Two shapes are declined (`nil`):
|
|
1524
1810
|
# - several overloads. The body is typed once, so the inferred return covers every overload together and
|
|
1525
1811
|
# says nothing about which overload returns what: collapsing them into one line would drop the
|
|
1526
1812
|
# overloads, and giving each the joint return would widen the overloads the author wrote narrower.
|
|
1527
1813
|
# - a name the class's own `sig/` spells as an `alias`. Its parameters are the aliased method's, and a
|
|
1528
1814
|
# proposal would read as a rewrite of that method's line rather than of the alias. An alias only an
|
|
1529
1815
|
# ancestor declares takes the override path below, like any other ancestor declaration.
|
|
1530
|
-
#
|
|
1531
|
-
#
|
|
1532
|
-
#
|
|
1816
|
+
# A method this class does not declare is not declined: when only an ancestor declares it, the proposal is a
|
|
1817
|
+
# new override on this class, rendered from the `def`'s own shape like any new method. The ancestor's
|
|
1818
|
+
# parameters describe the ancestor's `def`, not this one, which may take other arguments (ADR-14 clause 2).
|
|
1533
1819
|
def tighter_return_line(def_node, method_def, class_name, kind, inferred)
|
|
1534
1820
|
member = signature_member(method_def, class_name)
|
|
1535
1821
|
return render_rbs_line(def_node, inferred, class_name, kind) if member.nil?
|
|
@@ -1912,8 +2198,35 @@ module Rigor
|
|
|
1912
2198
|
# `owner:` is the class or module the rendered member belongs to. {AliasIndex} needs it to judge whether
|
|
1913
2199
|
# a matching alias is one this reader would expect to see; a call site that cannot supply it renders the
|
|
1914
2200
|
# type unfolded, which is the same output as before #1002.
|
|
2201
|
+
#
|
|
2202
|
+
# Issue #2230 — qualified last, after the alias fold has matched the unqualified spelling it keys on, so a core
|
|
2203
|
+
# name a namespace of the owner shadows (`Set` inside a `module Foo` that declares `Foo::Set`) is written `::Set`.
|
|
1915
2204
|
def elaborated_rbs(type, owner: nil)
|
|
1916
|
-
alias_index.fold(TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs, owner)
|
|
2205
|
+
folded = alias_index.fold(TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs, owner)
|
|
2206
|
+
captured = owner_type_param_names(owner)
|
|
2207
|
+
TypeNameQualification.qualify(folded, owner) { |name| captured.include?(name) || declared_constant?(name) }
|
|
2208
|
+
end
|
|
2209
|
+
|
|
2210
|
+
# Issue #2436 — inside `class Array[unchecked out E]` a bare `E` names the type variable, not a class `::E`,
|
|
2211
|
+
# so a class whose spelling equals one of the owner's declared type-parameter names is anchored. The names
|
|
2212
|
+
# come as `"Array::E"`, the form {TypeNameQualification.qualify} asks about for the owner's own namespace;
|
|
2213
|
+
# a namespace the owner is nested in does not bind its parameters inside the owner's declaration.
|
|
2214
|
+
def owner_type_param_names(owner)
|
|
2215
|
+
loader = owner && @environment&.rbs_loader
|
|
2216
|
+
return Set.new if loader.nil?
|
|
2217
|
+
|
|
2218
|
+
@owner_type_param_names ||= {}
|
|
2219
|
+
@owner_type_param_names.fetch(owner) do
|
|
2220
|
+
@owner_type_param_names[owner] = loader.class_declaration_type_params(owner).to_set do |p|
|
|
2221
|
+
"#{owner}::#{p.name}"
|
|
2222
|
+
end
|
|
2223
|
+
end
|
|
2224
|
+
end
|
|
2225
|
+
|
|
2226
|
+
# Whether a type name written as `name` resolves to a constant the run can see: one the RBS environment
|
|
2227
|
+
# declares, or a class or module the project's source declares, whether or not `sig/` has caught up with it.
|
|
2228
|
+
def declared_constant?(name)
|
|
2229
|
+
class_known?(name) || Reflection.discovered_class?(name, scope: file_base_scope(@environment))
|
|
1917
2230
|
end
|
|
1918
2231
|
|
|
1919
2232
|
# Issue #1002 — the reverse of {#translate_method_type_return}'s `alias_expander:`. Folding happens here,
|
|
@@ -2044,7 +2357,7 @@ module Rigor
|
|
|
2044
2357
|
return
|
|
2045
2358
|
end
|
|
2046
2359
|
when Prism::CallNode
|
|
2047
|
-
return if
|
|
2360
|
+
return if skipped_block_call?(node)
|
|
2048
2361
|
|
|
2049
2362
|
collect_attr_call(node, prefix, in_singleton_class, ctx)
|
|
2050
2363
|
end
|
|
@@ -2151,8 +2464,8 @@ module Rigor
|
|
|
2151
2464
|
end
|
|
2152
2465
|
end
|
|
2153
2466
|
|
|
2154
|
-
#
|
|
2155
|
-
#
|
|
2467
|
+
# Records the observation-derived types of ivars assigned directly from `def initialize` parameters, keyed
|
|
2468
|
+
# by class name and then attribute name. Only populated when `@observations` is
|
|
2156
2469
|
# non-empty (i.e. `--params=observed` was supplied). Matches the pattern `@ivar_name = param_name` where
|
|
2157
2470
|
# `param_name` is a required / optional positional or keyword parameter of `initialize`.
|
|
2158
2471
|
#
|
|
@@ -2285,7 +2598,7 @@ module Rigor
|
|
|
2285
2598
|
node.rigor_each_child { |c| collect_init_ivar_obs(c, prefix, result) }
|
|
2286
2599
|
end
|
|
2287
2600
|
|
|
2288
|
-
#
|
|
2601
|
+
# Derives the observation-derived attribute types of a single `def initialize`, matching `@ivar = param_name`
|
|
2289
2602
|
# assignments against the available `[class_name, :initialize]` observations.
|
|
2290
2603
|
def ivar_obs_from_initialize(class_name, def_node)
|
|
2291
2604
|
obs_list = @observations[[class_name, :initialize]]
|
|
@@ -2300,8 +2613,8 @@ module Rigor
|
|
|
2300
2613
|
build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
|
|
2301
2614
|
end
|
|
2302
2615
|
|
|
2303
|
-
#
|
|
2304
|
-
#
|
|
2616
|
+
# Maps `{ ivar_name => param_name }` to attribute types by looking up each param's observation types and
|
|
2617
|
+
# unioning them.
|
|
2305
2618
|
def build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
|
|
2306
2619
|
ivar_to_param.filter_map do |ivar_name, param_name|
|
|
2307
2620
|
types = collect_param_obs_types(obs_list, param_name, param_index[param_name])
|