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
|
@@ -11,7 +11,7 @@ module Rigor
|
|
|
11
11
|
# - `target_path` — `.rbs` file the writer was responsible for (`nil` when the source path falls outside
|
|
12
12
|
# the project signature tree, in which case `action` is `:skipped_outside_sig_root`).
|
|
13
13
|
# - `action` — one of `:created` / `:updated` / `:noop` / `:skipped_outside_sig_root` /
|
|
14
|
-
# `:skipped_invalid_rbs` / `:skipped_invalid_encoding`.
|
|
14
|
+
# `:skipped_invalid_rbs` / `:skipped_invalid_encoding` / `:skipped_unparseable`.
|
|
15
15
|
# - `applied` — the {MethodCandidate}s that actually landed on disk.
|
|
16
16
|
# - `replaced` — the subset of `applied` that replaced an existing declaration rather than adding one
|
|
17
17
|
# (`--overwrite`'s tighter returns, and inline updates).
|
|
@@ -23,16 +23,20 @@ module Rigor
|
|
|
23
23
|
# target declaration already carries annotations, so its bytes were left alone
|
|
24
24
|
# (`sig.effect.left-unreadable`; see {Writer#splice_annotations}). The signature line itself was
|
|
25
25
|
# still updated.
|
|
26
|
+
# - `repaired_headers` — the existing class or module headers the writer gave a generic class's type
|
|
27
|
+
# parameters (`"Array[unchecked out E]"`), because the file opened it with none (#2580).
|
|
26
28
|
# - `error` — the refusal cause, when `action` is a refusal: for `:skipped_invalid_rbs` the file the writer
|
|
27
29
|
# assembled does not parse, so it was NOT written (writing it would poison the project's sig tree — the
|
|
28
30
|
# consumer quarantines an unparseable `.rbs`, taking every other type in that file down with it); for
|
|
29
31
|
# `:skipped_invalid_encoding` the EXISTING target file is not valid UTF-8, so the writer refuses to merge
|
|
30
|
-
# into content it cannot read faithfully
|
|
32
|
+
# into content it cannot read faithfully; for `:skipped_unparseable` the EXISTING target file does not parse
|
|
33
|
+
# as RBS, so the writer refuses to merge into it (#2480).
|
|
31
34
|
class WriteResult
|
|
32
|
-
attr_reader :source_path, :target_path, :action, :applied, :skipped, :error, :left_unreadable, :replaced
|
|
35
|
+
attr_reader :source_path, :target_path, :action, :applied, :skipped, :error, :left_unreadable, :replaced,
|
|
36
|
+
:repaired_headers
|
|
33
37
|
|
|
34
38
|
def initialize(source_path:, target_path:, action:, applied: [], skipped: [], error: nil, # rubocop:disable Metrics/ParameterLists
|
|
35
|
-
left_unreadable: [], replaced: [])
|
|
39
|
+
left_unreadable: [], replaced: [], repaired_headers: [])
|
|
36
40
|
@source_path = source_path
|
|
37
41
|
@target_path = target_path
|
|
38
42
|
@action = action
|
|
@@ -41,6 +45,7 @@ module Rigor
|
|
|
41
45
|
@error = error
|
|
42
46
|
@left_unreadable = left_unreadable.freeze
|
|
43
47
|
@replaced = replaced.freeze
|
|
48
|
+
@repaired_headers = repaired_headers.freeze
|
|
44
49
|
freeze
|
|
45
50
|
end
|
|
46
51
|
|
|
@@ -51,14 +56,20 @@ module Rigor
|
|
|
51
56
|
action: action.to_s,
|
|
52
57
|
applied: applied.map(&:to_h),
|
|
53
58
|
skipped: skipped.map { |c, reason| c.to_h.merge(write_skip_reason: reason.to_s) }
|
|
54
|
-
}.tap
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
}.tap { |h| add_optional_entries(h) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def add_optional_entries(hash)
|
|
65
|
+
hash[:error] = error if error
|
|
66
|
+
# Absent when nothing was replaced, so a run that only adds lines keeps its pre-#1076 payload.
|
|
67
|
+
hash[:replaced] = replaced.map(&:to_h) unless replaced.empty?
|
|
68
|
+
# Absent when no header was repaired (#2580), for the same reason.
|
|
69
|
+
hash[:repaired_headers] = repaired_headers unless repaired_headers.empty?
|
|
70
|
+
# Absent rather than empty when nothing was left: an effects-off payload stays byte-identical
|
|
71
|
+
# to a pre-#391 one.
|
|
72
|
+
hash[:effect_left_unreadable] = left_unreadable.map(&:to_h) unless left_unreadable.empty?
|
|
62
73
|
end
|
|
63
74
|
end
|
|
64
75
|
end
|
data/lib/rigor/sig_gen/writer.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "fileutils"
|
|
|
4
4
|
require "rbs"
|
|
5
5
|
|
|
6
6
|
require_relative "classification"
|
|
7
|
+
require_relative "generic_header_scan"
|
|
7
8
|
require_relative "layout_index"
|
|
8
9
|
require_relative "superclass_spelling"
|
|
9
10
|
require_relative "write_result"
|
|
@@ -15,7 +16,8 @@ module Rigor
|
|
|
15
16
|
#
|
|
16
17
|
# ADR-14 slice 2: the writer parses the target with `RBS::Parser` to find the matching class declaration and
|
|
17
18
|
# inserts new method declarations just before the class's closing `end` keyword. Existing declarations are
|
|
18
|
-
# NEVER touched unless `--overwrite` is set
|
|
19
|
+
# NEVER touched unless `--overwrite` is set and the candidate is eligible for replacement (see
|
|
20
|
+
# {#eligible_for_replacement?}).
|
|
19
21
|
#
|
|
20
22
|
# The slice does NOT re-render the whole file through `RBS::Writer`. That would lose comments / blank-line
|
|
21
23
|
# formatting per upstream design; the byte-range insertion approach taken here preserves untouched
|
|
@@ -32,9 +34,18 @@ module Rigor
|
|
|
32
34
|
|
|
33
35
|
# Per-`update_existing` accumulator. The merge_class helper mutates `source` / `decls` / `applied` /
|
|
34
36
|
# `skipped` in place as each class is processed so the next class sees the latest byte positions.
|
|
35
|
-
MergeState = Struct.new(:source, :decls, :applied, :skipped, :left_unreadable, :replaced,
|
|
37
|
+
MergeState = Struct.new(:source, :decls, :applied, :skipped, :left_unreadable, :replaced, :repaired_headers,
|
|
38
|
+
:target, keyword_init: true)
|
|
36
39
|
private_constant :MergeState
|
|
37
40
|
|
|
41
|
+
# The target routing this writer applies; the generator maps candidates through it to ask {#accepts_target?}.
|
|
42
|
+
attr_reader :path_mapper
|
|
43
|
+
|
|
44
|
+
# #2605 — the generic classes whose bare headers the last {#write_all} (or {#write}) left bare, because a
|
|
45
|
+
# body names one of the parameters: `{ class:, targets:, blocking: [{ target:, names: }] }` each
|
|
46
|
+
# ({GenericHeaderScan#left_bare}).
|
|
47
|
+
attr_reader :left_bare
|
|
48
|
+
|
|
38
49
|
# @param dry_run — assemble and validate every target exactly as a write would, and report it, without
|
|
39
50
|
# creating or changing a file. `sig-gen --check` (ADR-112 WD4) is this, so it can only ever disagree
|
|
40
51
|
# with `--write` about the disk, never about the content.
|
|
@@ -47,6 +58,15 @@ module Rigor
|
|
|
47
58
|
@global_namespace_kinds = {}
|
|
48
59
|
# Run-level qualified-class-name => superclass-token view, same lifecycle as `@global_namespace_kinds`.
|
|
49
60
|
@global_superclasses = {}
|
|
61
|
+
# #2436 — the target being rendered's view of {MethodCandidate#class_type_params}, set per target.
|
|
62
|
+
@type_params = {}
|
|
63
|
+
# #2603/#2605 — the run-wide repair decision {#write_all} reads from the sig root before writing: the
|
|
64
|
+
# classes cleared for repair with their spelling, and the classes held back. {#write} leaves both empty and
|
|
65
|
+
# decides per file.
|
|
66
|
+
@scan_repairs = {}
|
|
67
|
+
@held_back = []
|
|
68
|
+
@left_bare = []
|
|
69
|
+
@repair_targets = []
|
|
50
70
|
end
|
|
51
71
|
|
|
52
72
|
# Process the full candidate list by resolving each candidate's target sig file via the path mapper (which
|
|
@@ -56,31 +76,94 @@ module Rigor
|
|
|
56
76
|
# ADR-14 follow-up: this is the consolidated-layout entry point. The legacy `write(source_path,
|
|
57
77
|
# candidates)` below assumes all candidates share a target and remains for spec convenience.
|
|
58
78
|
#
|
|
79
|
+
# @param header_spelling — called with a class name, returns the header spelling of a generic class the run
|
|
80
|
+
# does not stamp (#2603: a bare header in a file no candidate routes to); the generator's
|
|
81
|
+
# {Generator#generic_header_spelling}. Without it only the run's stamp counts.
|
|
59
82
|
# @return one per target sig file.
|
|
60
|
-
def write_all(candidates)
|
|
83
|
+
def write_all(candidates, header_spelling: nil)
|
|
61
84
|
emittable = candidates.select { |c| EMITTABLE.include?(c.classification) }
|
|
62
|
-
|
|
85
|
+
scan_generic_headers(candidates, header_spelling)
|
|
63
86
|
|
|
64
87
|
@global_namespace_kinds = build_namespace_kinds(candidates)
|
|
65
88
|
@global_superclasses = build_superclasses(candidates)
|
|
66
|
-
grouped = emittable.group_by
|
|
67
|
-
|
|
89
|
+
grouped = emittable.group_by { |c| target_of(c) }
|
|
90
|
+
results = grouped.map { |target, group| write_target(target, group) }
|
|
91
|
+
results + repair_only_results(candidates, grouped.keys.map { |target| target.expand_path.to_s })
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def target_of(candidate)
|
|
95
|
+
@path_mapper.target_for(candidate.path, class_name: candidate.class_name,
|
|
96
|
+
method_name: candidate.method_name, kind: candidate.rbs_kind)
|
|
97
|
+
end
|
|
98
|
+
private :target_of
|
|
99
|
+
|
|
100
|
+
# Issues #2603 and #2605 — reads every file under the signature paths for bare openings of generic classes
|
|
101
|
+
# before anything is written ({GenericHeaderScan}). A class is repaired in every file or in none: one bare
|
|
102
|
+
# opening rbs still rejects keeps the class failing, so repairing its other openings would be churn. Only files
|
|
103
|
+
# under the sig root are rewritten; a bare opening anywhere else holds its class back.
|
|
104
|
+
def scan_generic_headers(candidates, header_spelling)
|
|
105
|
+
stamp = merged_type_params(candidates)
|
|
106
|
+
scan = GenericHeaderScan.scan(sig_root: @path_mapper.sig_root_dir, signature_dirs: @path_mapper.signature_dirs,
|
|
107
|
+
spelling_for: ->(name) { stamp[name] || header_spelling&.call(name) })
|
|
108
|
+
@scan_repairs = scan.repairs
|
|
109
|
+
@held_back = scan.left_bare.map { |entry| entry.fetch(:class) }
|
|
110
|
+
@left_bare = scan.left_bare
|
|
111
|
+
@repair_targets = scan.repair_targets
|
|
112
|
+
end
|
|
113
|
+
private :scan_generic_headers
|
|
114
|
+
|
|
115
|
+
# The files with a bare header to repair that no emitted member routes to: each gets the repair and nothing
|
|
116
|
+
# else (#2580), whether a skipped candidate routes to it or none does (#2603). The row names a candidate's
|
|
117
|
+
# source when one routes there. A file the writer would not open (not UTF-8, unparseable) is never listed.
|
|
118
|
+
def repair_only_results(candidates, written)
|
|
119
|
+
sources = candidates.each_with_object({}) do |candidate, acc|
|
|
120
|
+
acc[target_of(candidate).expand_path.to_s] ||= candidate.path if candidate.path
|
|
121
|
+
end
|
|
122
|
+
@repair_targets.filter_map do |path|
|
|
123
|
+
target = Pathname(path)
|
|
124
|
+
next if written.include?(target.expand_path.to_s) || !inside_sig_root?(target)
|
|
125
|
+
|
|
126
|
+
result = update_existing(sources[target.expand_path.to_s], target, [], repair_only: true)
|
|
127
|
+
result if %i[updated skipped_invalid_rbs].include?(result.action)
|
|
68
128
|
end
|
|
69
|
-
grouped.map { |target, group| write_target(target, group) }
|
|
70
129
|
end
|
|
130
|
+
private :repair_only_results
|
|
71
131
|
|
|
72
|
-
# @param candidates — only
|
|
73
|
-
# emittable classifications (new-method /
|
|
74
|
-
# tighter-return) are honoured; the caller is
|
|
75
|
-
# responsible for filtering.
|
|
132
|
+
# @param candidates — only emittable classifications are honoured; `write` filters on `EMITTABLE` itself.
|
|
76
133
|
def write(source_path, candidates)
|
|
77
134
|
emittable = candidates.select { |c| EMITTABLE.include?(c.classification) }
|
|
78
135
|
return WriteResult.new(source_path: source_path, target_path: nil, action: :noop) if emittable.empty?
|
|
79
136
|
|
|
137
|
+
@scan_repairs = {}
|
|
138
|
+
@held_back = []
|
|
139
|
+
@left_bare = []
|
|
80
140
|
target = @path_mapper.target_for(source_path, class_name: emittable.first.class_name)
|
|
81
141
|
write_target(target, emittable, source_path: source_path)
|
|
82
142
|
end
|
|
83
143
|
|
|
144
|
+
# Whether the writer is sure to write `candidate`. A file the writer writes nothing to is left as it is, so a
|
|
145
|
+
# class shell riding on that file's candidates never reaches disk; the generator asks this before it counts
|
|
146
|
+
# a shell as declared (#2454). Only a new method is sure: without `--overwrite` the writer skips a tighter
|
|
147
|
+
# return or an inline overwrite whose method the target already declares. Counting too few only demotes a
|
|
148
|
+
# subclass, which is the output before #2454.
|
|
149
|
+
def self.writes?(candidate)
|
|
150
|
+
candidate.classification == Classification::NEW_METHOD
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Whether {#write_target} would write to `target` at all, asked before any candidate is rendered (#2461): a
|
|
154
|
+
# target inside the sig root that is missing (the create path), or that exists, is valid UTF-8 and parses.
|
|
155
|
+
# {#update_existing} refuses an unparseable or mis-encoded file and leaves it untouched, so a class shell on it
|
|
156
|
+
# never reaches disk. A spliced result {RbsValidity} rejects is a writer defect and is not predicted here.
|
|
157
|
+
def accepts_target?(target)
|
|
158
|
+
return false unless inside_sig_root?(target)
|
|
159
|
+
return true unless target.exist?
|
|
160
|
+
|
|
161
|
+
source = target.read
|
|
162
|
+
source.valid_encoding? && !parse_signature(source).nil?
|
|
163
|
+
rescue SystemCallError
|
|
164
|
+
false
|
|
165
|
+
end
|
|
166
|
+
|
|
84
167
|
# Whether `proposed` has STRICTLY FEWER bare `untyped` tokens than `existing`. A count, not a slot-by-slot
|
|
85
168
|
# comparison: it reads as "at least one more position is concrete", and does not prove that no concrete
|
|
86
169
|
# position became `untyped` elsewhere. Word-boundary matching counts `untyped` only as a type token, not
|
|
@@ -157,10 +240,11 @@ module Rigor
|
|
|
157
240
|
tree = build_namespace_tree(candidates, shells)
|
|
158
241
|
kinds = merged_namespace_kinds(candidates)
|
|
159
242
|
supers = merged_superclasses(candidates)
|
|
243
|
+
@type_params = merged_type_params(candidates)
|
|
160
244
|
render_tree_nodes(tree, kinds, supers, 0)
|
|
161
245
|
end
|
|
162
246
|
|
|
163
|
-
#
|
|
247
|
+
# Unions `class_shells` across every candidate; the generator's walker populates the same set on every
|
|
164
248
|
# candidate produced from a given file (gap-#3 (e)).
|
|
165
249
|
def collect_class_shells(candidates)
|
|
166
250
|
shells = Set.new
|
|
@@ -202,6 +286,13 @@ module Rigor
|
|
|
202
286
|
merged
|
|
203
287
|
end
|
|
204
288
|
|
|
289
|
+
# #2436 — every candidate carries the run's map, so folding the target's candidates is the whole view.
|
|
290
|
+
def merged_type_params(candidates)
|
|
291
|
+
candidates.each_with_object({}) do |c, acc|
|
|
292
|
+
acc.merge!(c.class_type_params) if c.respond_to?(:class_type_params)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
205
296
|
def build_superclasses(candidates)
|
|
206
297
|
candidates.each_with_object({}) do |candidate, acc|
|
|
207
298
|
next unless candidate.respond_to?(:class_superclasses)
|
|
@@ -257,7 +348,7 @@ module Rigor
|
|
|
257
348
|
indent = INDENT * depth
|
|
258
349
|
qualified = (prefix + [node[:name]]).join("::")
|
|
259
350
|
keyword = node_keyword(node, kinds, qualified)
|
|
260
|
-
header = "#{keyword} #{node[:name]}#{superclass_suffix(keyword, supers, qualified)}"
|
|
351
|
+
header = "#{keyword} #{node[:name]}#{@type_params[qualified]}#{superclass_suffix(keyword, supers, qualified)}"
|
|
261
352
|
body = render_tree_node_body(node, kinds, supers, depth, prefix)
|
|
262
353
|
"#{indent}#{header}\n#{body}#{indent}end\n"
|
|
263
354
|
end
|
|
@@ -293,13 +384,12 @@ module Rigor
|
|
|
293
384
|
:module
|
|
294
385
|
end
|
|
295
386
|
|
|
296
|
-
def update_existing(source_path, target, candidates)
|
|
387
|
+
def update_existing(source_path, target, candidates, repair_only: false)
|
|
297
388
|
source = target.read
|
|
298
389
|
# Pre-parser encoding guard, mirroring `Environment::RbsLoader.invalid_encoding?`: handed invalid
|
|
299
390
|
# UTF-8, `RBS::Parser` raises a bare `ArgumentError` on rbs 4.1 (not the `ParsingError` that
|
|
300
391
|
# `parse_signature` below rescues) and could hang outright on the older releases the gemspec
|
|
301
|
-
# supports. The refusal is loud —
|
|
302
|
-
# own quarantine warning), a mojibake file would otherwise fail with a stack trace naming neither
|
|
392
|
+
# supports. The refusal is loud — a mojibake file would otherwise fail with a stack trace naming neither
|
|
303
393
|
# the file nor the fix.
|
|
304
394
|
unless source.valid_encoding?
|
|
305
395
|
return WriteResult.new(source_path: source_path, target_path: target,
|
|
@@ -307,16 +397,19 @@ module Rigor
|
|
|
307
397
|
error: "#{target} is not valid UTF-8")
|
|
308
398
|
end
|
|
309
399
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
400
|
+
# An existing file that does not parse is refused like a mis-encoded one, not skipped as `:noop` (#2480):
|
|
401
|
+
# `--check` must fail on it, because a `--write` that cannot touch the file is not an up-to-date `sig/`.
|
|
402
|
+
# `rigor check` quarantines such a file whole; this refusal names it at the sig-gen boundary too.
|
|
403
|
+
decls, parse_detail = parse_target_signature(target, source)
|
|
404
|
+
if decls.nil?
|
|
405
|
+
return WriteResult.new(source_path: source_path, target_path: target,
|
|
406
|
+
action: :skipped_unparseable,
|
|
407
|
+
error: "#{target} does not parse as RBS: #{parse_detail}")
|
|
408
|
+
end
|
|
316
409
|
|
|
317
|
-
|
|
318
|
-
unless
|
|
319
|
-
return WriteResult.new(source_path: source_path, target_path: target, action:
|
|
410
|
+
state = merged_state(source, decls, candidates, repair_only, target)
|
|
411
|
+
unless state_changed?(state)
|
|
412
|
+
return WriteResult.new(source_path: source_path, target_path: target, action: :noop,
|
|
320
413
|
applied: state.applied, skipped: state.skipped,
|
|
321
414
|
left_unreadable: state.left_unreadable)
|
|
322
415
|
end
|
|
@@ -333,8 +426,9 @@ module Rigor
|
|
|
333
426
|
|
|
334
427
|
target.write(state.source) unless @dry_run
|
|
335
428
|
WriteResult.new(source_path: source_path, target_path: target,
|
|
336
|
-
action:
|
|
337
|
-
left_unreadable: state.left_unreadable, replaced: state.replaced
|
|
429
|
+
action: :updated, applied: state.applied, skipped: state.skipped,
|
|
430
|
+
left_unreadable: state.left_unreadable, replaced: state.replaced,
|
|
431
|
+
repaired_headers: state.repaired_headers)
|
|
338
432
|
end
|
|
339
433
|
|
|
340
434
|
# Applies every class group, then every requested shell, then normalises the layout the three steps
|
|
@@ -343,12 +437,89 @@ module Rigor
|
|
|
343
437
|
supers = merged_superclasses(candidates)
|
|
344
438
|
kinds = merged_namespace_kinds(candidates)
|
|
345
439
|
shells = collect_class_shells(candidates)
|
|
440
|
+
@type_params = merged_type_params(candidates)
|
|
346
441
|
groups = candidates.group_by(&:class_name)
|
|
347
442
|
# Ancestors first, so `Foo` is created (or found) before `Foo::Bar` looks for a parent to nest under.
|
|
348
443
|
# A plain sort suffices: a name always sorts before every name it is a strict prefix of.
|
|
349
444
|
groups.keys.sort.each { |name| merge_class(state, name, groups.fetch(name), kinds, supers) }
|
|
350
445
|
merge_class_shells(state, shells, kinds, supers)
|
|
351
446
|
collapse_nested_declarations(state, groups.keys + shells.to_a)
|
|
447
|
+
# Last, so the members this run merged count towards {#shadowed_after_merge?}.
|
|
448
|
+
repair_generic_headers(state, state.target)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Issue #2580 — a declaration this file already holds for a class the loaded RBS declares generic, opened
|
|
452
|
+
# with no type parameters at all (`class Array`, as sig-gen wrote it before #2436), is a guaranteed
|
|
453
|
+
# `RBS::GenericParameterMismatchError`: rbs requires every declaration of a class to repeat the first one's
|
|
454
|
+
# parameters, and the class, plus every class whose ancestry reaches it (`Object` through `Kernel`), then
|
|
455
|
+
# fails its definition build. Writing more members into that header would keep the file broken, so the
|
|
456
|
+
# header gets the same spelling a new header would ({Generator#stamp_generic_reopenings}: plain
|
|
457
|
+
# parameters only). A header that spells parameters of its own is the project's and is left alone, as is
|
|
458
|
+
# every class neither the run's stamp nor {#write_all}'s scan names.
|
|
459
|
+
#
|
|
460
|
+
# A declaration whose body names a type like one of the parameters (`def old: () -> E` for a project class
|
|
461
|
+
# `E`) is left bare: under `class Array[unchecked out E]` that `E` would bind to the parameter, silently
|
|
462
|
+
# changing what the member says. The body is matched as text, so a comment or a nested declaration that
|
|
463
|
+
# spells the name also keeps the header bare, which is the state the file was already in; a method type that
|
|
464
|
+
# declares the name as its own parameter (`def m: [E] (E) -> E`) does not (#2605).
|
|
465
|
+
#
|
|
466
|
+
# Under {#write_all} the class is repaired in every file under the sig root or in none ({GenericHeaderScan},
|
|
467
|
+
# #2605): a class held back there is not repaired here, and a class the run does not stamp is repaired with the
|
|
468
|
+
# scan's spelling (#2603). The check below runs again after the merge, so a member this run merged counts
|
|
469
|
+
# too; the generator anchors a member's type that a parameter would capture (#2436), so it holds back a class
|
|
470
|
+
# the scan cleared only for a member written without that anchor, and is then reported in {#left_bare}.
|
|
471
|
+
# `repaired_headers` gets one entry per declaration repaired.
|
|
472
|
+
def repair_generic_headers(state, target)
|
|
473
|
+
spellings = @scan_repairs.merge(@type_params).except(*@held_back)
|
|
474
|
+
spellings.keys.sort.each do |name|
|
|
475
|
+
spelling = spellings.fetch(name)
|
|
476
|
+
bare = find_class_decls(state.decls, name).select { |d| d.type_params.empty? && d.location&.[](:name) }
|
|
477
|
+
next if bare.empty? || shadowed_after_merge?(state.source, bare, name, spelling, target)
|
|
478
|
+
|
|
479
|
+
insert_type_params(state, bare, spelling)
|
|
480
|
+
bare.each { state.repaired_headers << "#{name}#{spelling}" }
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# Whether any of `bare` names one of the parameters `spelling` declares ({GenericHeaderScan.shadowing_names});
|
|
485
|
+
# each such opening is recorded in {#left_bare}.
|
|
486
|
+
def shadowed_after_merge?(source, bare, name, spelling, target)
|
|
487
|
+
params = GenericHeaderScan.parameter_names(spelling)
|
|
488
|
+
return true if params.nil?
|
|
489
|
+
|
|
490
|
+
blocking = bare.filter_map do |decl|
|
|
491
|
+
names = GenericHeaderScan.shadowing_names(source, decl, params)
|
|
492
|
+
{ target: target.to_s, reason: "names_parameter", names: names } unless names.empty?
|
|
493
|
+
end
|
|
494
|
+
return false if blocking.empty?
|
|
495
|
+
|
|
496
|
+
@left_bare << { class: name, targets: [target.to_s], blocking: blocking }
|
|
497
|
+
true
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Splices `spelling` right after each declaration's name, last one first so earlier offsets stay valid.
|
|
501
|
+
def insert_type_params(state, decls, spelling)
|
|
502
|
+
decls.map { |d| d.location[:name].end_pos }.sort.reverse_each do |pos|
|
|
503
|
+
state.source = state.source[0...pos] + spelling + state.source[pos..]
|
|
504
|
+
end
|
|
505
|
+
state.decls = parse_signature(state.source) || state.decls
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def merged_state(source, decls, candidates, repair_only, target)
|
|
509
|
+
state = MergeState.new(source: source, decls: decls, applied: [], skipped: [], left_unreadable: [],
|
|
510
|
+
replaced: [], repaired_headers: [], target: target)
|
|
511
|
+
repair_only ? repair_only_candidates(state, candidates) : merge_candidates(state, candidates)
|
|
512
|
+
state
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def state_changed?(state)
|
|
516
|
+
!(state.applied.empty? && state.repaired_headers.empty?)
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# {#repair_only_results}' merge step: the run's stamp, and the header repair alone.
|
|
520
|
+
def repair_only_candidates(state, candidates)
|
|
521
|
+
@type_params = merged_type_params(candidates)
|
|
522
|
+
repair_generic_headers(state, state.target)
|
|
352
523
|
end
|
|
353
524
|
|
|
354
525
|
# ADR-14 gap-#3 (e): for every requested class shell that isn't already declared in the target file,
|
|
@@ -401,7 +572,9 @@ module Rigor
|
|
|
401
572
|
parent_name = segments[0...-1].join("::")
|
|
402
573
|
parent = find_class_decl(state.decls, parent_name)
|
|
403
574
|
decl = parent && find_class_decl(state.decls, qualified)
|
|
404
|
-
|
|
575
|
+
# A rooted declaration (`module A; class ::B::C`) keeps the layout the user wrote: cutting it out of its
|
|
576
|
+
# enclosing module would leave that module empty, so the member merges in place instead.
|
|
577
|
+
return if decl.nil? || overlapping?(parent, decl) || decl.name.to_s.start_with?("::")
|
|
405
578
|
|
|
406
579
|
region = decl_region(state.source, decl)
|
|
407
580
|
block = region && relocated_block(state.source, decl, parent, segments.last, region)
|
|
@@ -560,6 +733,15 @@ module Rigor
|
|
|
560
733
|
nil
|
|
561
734
|
end
|
|
562
735
|
|
|
736
|
+
# Parses an EXISTING target, naming it as the buffer so the parse error carries `path:line:col` (a bare
|
|
737
|
+
# String would be named `a.rbs`). Returns `[decls, nil]`, or `[nil, first_line_of_message]` on a parse error.
|
|
738
|
+
def parse_target_signature(target, source)
|
|
739
|
+
_, _, decls = RBS::Parser.parse_signature(RBS::Buffer.new(name: target.to_s, content: source))
|
|
740
|
+
[decls, nil]
|
|
741
|
+
rescue RBS::ParsingError => e
|
|
742
|
+
[nil, e.message.to_s.lines.first.to_s.strip]
|
|
743
|
+
end
|
|
744
|
+
|
|
563
745
|
def merge_class(state, class_name, methods, kinds, supers)
|
|
564
746
|
decl = find_class_decl(state.decls, class_name)
|
|
565
747
|
if decl.nil?
|
|
@@ -586,11 +768,10 @@ module Rigor
|
|
|
586
768
|
decls.each do |decl|
|
|
587
769
|
next unless decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)
|
|
588
770
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return decl if full == qualified_name
|
|
771
|
+
path = ::Rigor::Environment::RbsLoader.declared_class_path(decl, prefix)
|
|
772
|
+
return decl if path.join("::") == qualified_name
|
|
592
773
|
|
|
593
|
-
nested = find_class_decl_in(decl.members,
|
|
774
|
+
nested = find_class_decl_in(decl.members, path, qualified_name)
|
|
594
775
|
return nested if nested
|
|
595
776
|
end
|
|
596
777
|
nil
|
|
@@ -602,10 +783,9 @@ module Rigor
|
|
|
602
783
|
decls.each do |decl|
|
|
603
784
|
next unless decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)
|
|
604
785
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
find_class_decls(decl.members, qualified_name, prefix + [local], found)
|
|
786
|
+
path = ::Rigor::Environment::RbsLoader.declared_class_path(decl, prefix)
|
|
787
|
+
found << decl if path.join("::") == qualified_name
|
|
788
|
+
find_class_decls(decl.members, qualified_name, path, found)
|
|
609
789
|
end
|
|
610
790
|
found
|
|
611
791
|
end
|
|
@@ -669,7 +849,7 @@ module Rigor
|
|
|
669
849
|
# name, slices out the old declaration's source range and substitutes the new RBS one-liner. Members that
|
|
670
850
|
# are not `MethodDefinition`s are left alone.
|
|
671
851
|
#
|
|
672
|
-
#
|
|
852
|
+
# Under `--overwrite`, an inline overwrite is always eligible, and so are two other classifications:
|
|
673
853
|
#
|
|
674
854
|
# 1. `TIGHTER_RETURN` — the classifier already proved the new return type is a strict subtype of the
|
|
675
855
|
# declared one (with lenience guards passed).
|
|
@@ -6,8 +6,8 @@ module Rigor
|
|
|
6
6
|
#
|
|
7
7
|
# Every carrier's acceptance check is the same fixed forwarding on `self` —
|
|
8
8
|
# `Inference::Acceptance.accepts(self, other, mode: mode)` — so it lived as an identical copy in
|
|
9
|
-
#
|
|
10
|
-
# than `self`; it keeps its own `accepts` and does not include this.
|
|
9
|
+
# every carrier that includes it. The one exception is `Type::App`, which forwards on its reduced `bound`
|
|
10
|
+
# type rather than `self`; it keeps its own `accepts` and does not include this.
|
|
11
11
|
module AcceptanceRouter
|
|
12
12
|
def accepts(other, mode: :gradual)
|
|
13
13
|
Inference::Acceptance.accepts(self, other, mode: mode)
|
|
@@ -9,7 +9,7 @@ module Rigor
|
|
|
9
9
|
# answer alongside the boundary mode the answer was computed under and an ordered list of textual
|
|
10
10
|
# reasons describing which rules fired.
|
|
11
11
|
#
|
|
12
|
-
# AcceptsResult is the dual of `SubtypeResult
|
|
12
|
+
# AcceptsResult is the dual of the planned `SubtypeResult`. Acceptance answers "is `other` passable to
|
|
13
13
|
# `self` at a method-parameter or assignment boundary?", consulting the gradual-typing rules in
|
|
14
14
|
# docs/type-specification/value-lattice.md when `mode` is `:gradual`, and the strict subset relation
|
|
15
15
|
# when `mode` is `:strict`. Phase 2c ships full `:gradual` semantics; `:strict` is reserved for later
|
data/lib/rigor/type/app.rb
CHANGED
|
@@ -17,13 +17,11 @@ module Rigor
|
|
|
17
17
|
# (use a plain type alias instead).
|
|
18
18
|
#
|
|
19
19
|
# `bound` is a `Rigor::Type` representing the value Rigor MUST erase to when this `App` cannot be
|
|
20
|
-
# reduced — registered at `%a{rigor:v1:hkt_register}` time, defaulting to
|
|
21
|
-
#
|
|
22
|
-
# (`top` / `bot` / `dynamic`) and the acceptance fallback while no reduction is wired (Slice 1).
|
|
20
|
+
# reduced — registered at `%a{rigor:v1:hkt_register}` time, defaulting to the untyped top per ADR-20 D5 /
|
|
21
|
+
# WD2. It also drives the lattice probes (`top` / `bot` / `dynamic`) and `accepts`.
|
|
23
22
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# to its registered body before delegating; the carrier shape stays identical.
|
|
23
|
+
# The carrier is opaque: those operations delegate to `bound`, and {#reduce} is the one path that unfolds
|
|
24
|
+
# the application to its registered body first (ADR-20 Slice 2a).
|
|
27
25
|
#
|
|
28
26
|
# Display form per ADR-20 OQ5: bare RBS-style `uri[arg1, arg2]`, not the wrapped `App[uri, [arg1,
|
|
29
27
|
# arg2]]` faithful form. Two `App` values are structurally equal iff their `uri`, `args`, AND `bound`
|
|
@@ -245,8 +245,8 @@ module Rigor
|
|
|
245
245
|
end
|
|
246
246
|
|
|
247
247
|
# `literal-string` — a `String` that is statically known to come from a source-code literal (or a
|
|
248
|
-
# composition of literals).
|
|
249
|
-
#
|
|
248
|
+
# composition of literals). Interpolation `"#{...}"` and `+` / `*` composition keep
|
|
249
|
+
# the flow (see {literal_string_compatible?}). Every `Constant<String>` is implicitly
|
|
250
250
|
# literal-string-compatible; the carrier exists for cases where the concrete value is unknown but
|
|
251
251
|
# literal-ness has been established (an RBS::Extended `return: literal-string` annotation, or
|
|
252
252
|
# interpolation over literal-bearing parts).
|
|
@@ -433,8 +433,7 @@ module Rigor
|
|
|
433
433
|
# - `Type::Tuple[A, B, C]` → `Constant<0> | Constant<1> | Constant<2>`.
|
|
434
434
|
# - `Type::Nominal["Hash", [K, V]]` → `K` (untyped if absent).
|
|
435
435
|
# - `Type::Nominal["Array", [E]]` → `non-negative-int`.
|
|
436
|
-
# - `Type::Constant`
|
|
437
|
-
# per-element keys.
|
|
436
|
+
# - `Type::Constant` wrapping a Range with an Integer begin — `non-negative-int`.
|
|
438
437
|
#
|
|
439
438
|
# Other inputs (`Top`, `Dynamic`, untyped Nominals, `Union`, `Refined`, `Difference`,
|
|
440
439
|
# `Intersection`) project to `top` so the type function always returns a value — callers may narrow
|
|
@@ -456,8 +455,7 @@ module Rigor
|
|
|
456
455
|
# - `Type::Tuple[A, B, C]` → `A | B | C`.
|
|
457
456
|
# - `Type::Nominal["Hash", [K, V]]` → `V` (untyped if absent).
|
|
458
457
|
# - `Type::Nominal["Array", [E]]` → `E` (untyped if absent).
|
|
459
|
-
# - `Type::Constant`
|
|
460
|
-
# element.
|
|
458
|
+
# - `Type::Constant` wrapping an integer Range — the `IntegerRange` it spans.
|
|
461
459
|
def value_of(type)
|
|
462
460
|
case type
|
|
463
461
|
when HashShape then hash_shape_values(type)
|
|
@@ -528,8 +526,7 @@ module Rigor
|
|
|
528
526
|
# Phase A handles `Type::HashShape` (literal-key K). Phase B (slice 5) extends to `Type::Tuple`
|
|
529
527
|
# (integer-index K) — `pick_of[Tuple[A, B, C], 0 | 2]` evaluates to `Tuple[A, C]`. Non-shape inputs
|
|
530
528
|
# (`Type::Nominal`, etc.) return `type` unchanged ("lossy degradation"; the
|
|
531
|
-
# `dynamic.shape.lossy-projection` diagnostic that flags the boundary
|
|
532
|
-
# diagnostic threading arrives).
|
|
529
|
+
# `dynamic.shape.lossy-projection` diagnostic that flags the boundary).
|
|
533
530
|
def pick_of(type, keys)
|
|
534
531
|
case type
|
|
535
532
|
when HashShape then hash_shape_pick(type, keys)
|
|
@@ -597,8 +594,7 @@ module Rigor
|
|
|
597
594
|
# site where the projection was authored.
|
|
598
595
|
#
|
|
599
596
|
# `HashShape` and `Tuple` carry shape-level information the projections honour; every other carrier
|
|
600
|
-
# is lossy.
|
|
601
|
-
# predicate stands alone in slice 5 for unit-test coverage and future composition.
|
|
597
|
+
# is lossy.
|
|
602
598
|
def shape_projection_lossy?(type)
|
|
603
599
|
!type.is_a?(HashShape) && !type.is_a?(Tuple)
|
|
604
600
|
end
|
|
@@ -792,9 +788,9 @@ module Rigor
|
|
|
792
788
|
top
|
|
793
789
|
end
|
|
794
790
|
|
|
795
|
-
# `Type::Constant` only carries scalar literals (
|
|
796
|
-
#
|
|
797
|
-
#
|
|
791
|
+
# `Type::Constant` only carries scalar literals (`Constant::SCALAR_CLASSES`); Array and Hash literals
|
|
792
|
+
# become Tuple / HashShape carriers earlier in the typer. Range is the only scalar with meaningful
|
|
793
|
+
# key/value projections.
|
|
798
794
|
def constant_keys(value)
|
|
799
795
|
return non_negative_int if value.is_a?(Range) && value.begin.is_a?(Integer)
|
|
800
796
|
|
data/lib/rigor/type/constant.rb
CHANGED
|
@@ -8,9 +8,8 @@ require_relative "plain_lattice"
|
|
|
8
8
|
module Rigor
|
|
9
9
|
module Type
|
|
10
10
|
# A literal carrier under ADR-3 OQ1 Option C (Hybrid). Wraps a Ruby literal value of one of the
|
|
11
|
-
# supported immutable-ish classes. Compound literal shapes (Tuple, HashShape
|
|
12
|
-
#
|
|
13
|
-
# tuple slicing.
|
|
11
|
+
# supported immutable-ish classes. Compound literal shapes (Tuple, HashShape) have their own classes;
|
|
12
|
+
# Range is carried only when both static endpoints are known enough for tuple slicing.
|
|
14
13
|
#
|
|
15
14
|
# See docs/adr/4-type-inference-engine.md for the tentative answer to the open question and
|
|
16
15
|
# docs/type-specification/rigor-extensions.md for the refinement neighbourhood this carrier lives in.
|
|
@@ -74,12 +73,12 @@ module Rigor
|
|
|
74
73
|
|
|
75
74
|
# RBS supports `Literal` types for booleans, nil, integer literals (positive and negative), symbol
|
|
76
75
|
# literals, and string literals. Erasing to these preserves the carrier's precision at the RBS
|
|
77
|
-
# boundary —
|
|
76
|
+
# boundary — the constant 64 round-trips as `64`, not as `Integer` — and
|
|
78
77
|
# `RbsTypeTranslator#translate_literal` already maps the parsed RBS Literal back to `Constant`.
|
|
79
78
|
# Scalar carriers without RBS Literal support (Float, Range, Rational, Complex, Regexp, Pathname)
|
|
80
79
|
# keep their pre-existing widen-to-class-name behaviour because RBS rejects their literal spellings
|
|
81
80
|
# as syntax errors. `Range` is generic (`Range[out E]`), and a bare `Range` fails `rbs validate`, so
|
|
82
|
-
# it carries the class of its endpoints, or `untyped` when both are nil (#1891). `Set
|
|
81
|
+
# it carries the class of its endpoints, or `untyped` when both are nil (#1891). `Set` is
|
|
83
82
|
# generic too, and a set is mutable: the constant's elements are only what it held when it was built, and
|
|
84
83
|
# `x << "a"` adds a member they do not name. It erases to `Set[untyped]`, as an Array or Hash literal's
|
|
85
84
|
# element type is widened rather than pinned (#2177).
|