rigortype 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +29 -10
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +92 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +81 -5
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +123 -25
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +287 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +48 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +81 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +32 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +238 -37
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +82 -3
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,33 @@ 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
|
+
# #2576 — a target the writer cannot write (a permission denial, a directory sitting where the
|
|
42
|
+
# `.rbs` belongs, a parent path that is a regular file) is a bad `--write` invocation, not a
|
|
43
|
+
# crash: the command
|
|
44
|
+
# reports it as the same usage error `rigor init --path` gives and exits 64. Carries the target
|
|
45
|
+
# and the stripped Errno reason so the CLI prints `cannot write PATH: REASON` without
|
|
46
|
+
# re-parsing the message.
|
|
47
|
+
class WriteError < StandardError
|
|
48
|
+
attr_reader :target_path
|
|
49
|
+
|
|
50
|
+
def initialize(target_path, error)
|
|
51
|
+
@target_path = target_path.to_s
|
|
52
|
+
super("cannot write #{@target_path}: #{error.message.sub(/ @ .*\z/m, '')}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The target routing this writer applies; the generator maps candidates through it to ask {#accepts_target?}.
|
|
57
|
+
attr_reader :path_mapper
|
|
58
|
+
|
|
59
|
+
# #2605 — the generic classes whose bare headers the last {#write_all} (or {#write}) left bare, because a
|
|
60
|
+
# body names one of the parameters: `{ class:, targets:, blocking: [{ target:, names: }] }` each
|
|
61
|
+
# ({GenericHeaderScan#left_bare}).
|
|
62
|
+
attr_reader :left_bare
|
|
63
|
+
|
|
38
64
|
# @param dry_run — assemble and validate every target exactly as a write would, and report it, without
|
|
39
65
|
# creating or changing a file. `sig-gen --check` (ADR-112 WD4) is this, so it can only ever disagree
|
|
40
66
|
# with `--write` about the disk, never about the content.
|
|
@@ -47,6 +73,15 @@ module Rigor
|
|
|
47
73
|
@global_namespace_kinds = {}
|
|
48
74
|
# Run-level qualified-class-name => superclass-token view, same lifecycle as `@global_namespace_kinds`.
|
|
49
75
|
@global_superclasses = {}
|
|
76
|
+
# #2436 — the target being rendered's view of {MethodCandidate#class_type_params}, set per target.
|
|
77
|
+
@type_params = {}
|
|
78
|
+
# #2603/#2605 — the run-wide repair decision {#write_all} reads from the sig root before writing: the
|
|
79
|
+
# classes cleared for repair with their spelling, and the classes held back. {#write} leaves both empty and
|
|
80
|
+
# decides per file.
|
|
81
|
+
@scan_repairs = {}
|
|
82
|
+
@held_back = []
|
|
83
|
+
@left_bare = []
|
|
84
|
+
@repair_targets = []
|
|
50
85
|
end
|
|
51
86
|
|
|
52
87
|
# Process the full candidate list by resolving each candidate's target sig file via the path mapper (which
|
|
@@ -56,31 +91,94 @@ module Rigor
|
|
|
56
91
|
# ADR-14 follow-up: this is the consolidated-layout entry point. The legacy `write(source_path,
|
|
57
92
|
# candidates)` below assumes all candidates share a target and remains for spec convenience.
|
|
58
93
|
#
|
|
94
|
+
# @param header_spelling — called with a class name, returns the header spelling of a generic class the run
|
|
95
|
+
# does not stamp (#2603: a bare header in a file no candidate routes to); the generator's
|
|
96
|
+
# {Generator#generic_header_spelling}. Without it only the run's stamp counts.
|
|
59
97
|
# @return one per target sig file.
|
|
60
|
-
def write_all(candidates)
|
|
98
|
+
def write_all(candidates, header_spelling: nil)
|
|
61
99
|
emittable = candidates.select { |c| EMITTABLE.include?(c.classification) }
|
|
62
|
-
|
|
100
|
+
scan_generic_headers(candidates, header_spelling)
|
|
63
101
|
|
|
64
102
|
@global_namespace_kinds = build_namespace_kinds(candidates)
|
|
65
103
|
@global_superclasses = build_superclasses(candidates)
|
|
66
|
-
grouped = emittable.group_by
|
|
67
|
-
|
|
104
|
+
grouped = emittable.group_by { |c| target_of(c) }
|
|
105
|
+
results = grouped.map { |target, group| write_target(target, group) }
|
|
106
|
+
results + repair_only_results(candidates, grouped.keys.map { |target| target.expand_path.to_s })
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def target_of(candidate)
|
|
110
|
+
@path_mapper.target_for(candidate.path, class_name: candidate.class_name,
|
|
111
|
+
method_name: candidate.method_name, kind: candidate.rbs_kind)
|
|
112
|
+
end
|
|
113
|
+
private :target_of
|
|
114
|
+
|
|
115
|
+
# Issues #2603 and #2605 — reads every file under the signature paths for bare openings of generic classes
|
|
116
|
+
# before anything is written ({GenericHeaderScan}). A class is repaired in every file or in none: one bare
|
|
117
|
+
# opening rbs still rejects keeps the class failing, so repairing its other openings would be churn. Only files
|
|
118
|
+
# under the sig root are rewritten; a bare opening anywhere else holds its class back.
|
|
119
|
+
def scan_generic_headers(candidates, header_spelling)
|
|
120
|
+
stamp = merged_type_params(candidates)
|
|
121
|
+
scan = GenericHeaderScan.scan(sig_root: @path_mapper.sig_root_dir, signature_dirs: @path_mapper.signature_dirs,
|
|
122
|
+
spelling_for: ->(name) { stamp[name] || header_spelling&.call(name) })
|
|
123
|
+
@scan_repairs = scan.repairs
|
|
124
|
+
@held_back = scan.left_bare.map { |entry| entry.fetch(:class) }
|
|
125
|
+
@left_bare = scan.left_bare
|
|
126
|
+
@repair_targets = scan.repair_targets
|
|
127
|
+
end
|
|
128
|
+
private :scan_generic_headers
|
|
129
|
+
|
|
130
|
+
# The files with a bare header to repair that no emitted member routes to: each gets the repair and nothing
|
|
131
|
+
# else (#2580), whether a skipped candidate routes to it or none does (#2603). The row names a candidate's
|
|
132
|
+
# source when one routes there. A file the writer would not open (not UTF-8, unparseable) is never listed.
|
|
133
|
+
def repair_only_results(candidates, written)
|
|
134
|
+
sources = candidates.each_with_object({}) do |candidate, acc|
|
|
135
|
+
acc[target_of(candidate).expand_path.to_s] ||= candidate.path if candidate.path
|
|
136
|
+
end
|
|
137
|
+
@repair_targets.filter_map do |path|
|
|
138
|
+
target = Pathname(path)
|
|
139
|
+
next if written.include?(target.expand_path.to_s) || !inside_sig_root?(target)
|
|
140
|
+
|
|
141
|
+
result = update_existing(sources[target.expand_path.to_s], target, [], repair_only: true)
|
|
142
|
+
result if %i[updated skipped_invalid_rbs].include?(result.action)
|
|
68
143
|
end
|
|
69
|
-
grouped.map { |target, group| write_target(target, group) }
|
|
70
144
|
end
|
|
145
|
+
private :repair_only_results
|
|
71
146
|
|
|
72
|
-
# @param candidates — only
|
|
73
|
-
# emittable classifications (new-method /
|
|
74
|
-
# tighter-return) are honoured; the caller is
|
|
75
|
-
# responsible for filtering.
|
|
147
|
+
# @param candidates — only emittable classifications are honoured; `write` filters on `EMITTABLE` itself.
|
|
76
148
|
def write(source_path, candidates)
|
|
77
149
|
emittable = candidates.select { |c| EMITTABLE.include?(c.classification) }
|
|
78
150
|
return WriteResult.new(source_path: source_path, target_path: nil, action: :noop) if emittable.empty?
|
|
79
151
|
|
|
152
|
+
@scan_repairs = {}
|
|
153
|
+
@held_back = []
|
|
154
|
+
@left_bare = []
|
|
80
155
|
target = @path_mapper.target_for(source_path, class_name: emittable.first.class_name)
|
|
81
156
|
write_target(target, emittable, source_path: source_path)
|
|
82
157
|
end
|
|
83
158
|
|
|
159
|
+
# Whether the writer is sure to write `candidate`. A file the writer writes nothing to is left as it is, so a
|
|
160
|
+
# class shell riding on that file's candidates never reaches disk; the generator asks this before it counts
|
|
161
|
+
# a shell as declared (#2454). Only a new method is sure: without `--overwrite` the writer skips a tighter
|
|
162
|
+
# return or an inline overwrite whose method the target already declares. Counting too few only demotes a
|
|
163
|
+
# subclass, which is the output before #2454.
|
|
164
|
+
def self.writes?(candidate)
|
|
165
|
+
candidate.classification == Classification::NEW_METHOD
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Whether {#write_target} would write to `target` at all, asked before any candidate is rendered (#2461): a
|
|
169
|
+
# target inside the sig root that is missing (the create path), or that exists, is valid UTF-8 and parses.
|
|
170
|
+
# {#update_existing} refuses an unparseable or mis-encoded file and leaves it untouched, so a class shell on it
|
|
171
|
+
# never reaches disk. A spliced result {RbsValidity} rejects is a writer defect and is not predicted here.
|
|
172
|
+
def accepts_target?(target)
|
|
173
|
+
return false unless inside_sig_root?(target)
|
|
174
|
+
return true unless target.exist?
|
|
175
|
+
|
|
176
|
+
source = target.read
|
|
177
|
+
source.valid_encoding? && !parse_signature(source).nil?
|
|
178
|
+
rescue SystemCallError
|
|
179
|
+
false
|
|
180
|
+
end
|
|
181
|
+
|
|
84
182
|
# Whether `proposed` has STRICTLY FEWER bare `untyped` tokens than `existing`. A count, not a slot-by-slot
|
|
85
183
|
# comparison: it reads as "at least one more position is concrete", and does not prove that no concrete
|
|
86
184
|
# position became `untyped` elsewhere. Word-boundary matching counts `untyped` only as a type token, not
|
|
@@ -103,6 +201,8 @@ module Rigor
|
|
|
103
201
|
end
|
|
104
202
|
|
|
105
203
|
target.exist? ? update_existing(source_path, target, candidates) : create_new(source_path, target, candidates)
|
|
204
|
+
rescue SystemCallError => e
|
|
205
|
+
raise WriteError.new(target, e)
|
|
106
206
|
end
|
|
107
207
|
|
|
108
208
|
# Deliberately NARROWER than {Classification::EMITTABLE}: the write path has never produced a
|
|
@@ -139,6 +239,8 @@ module Rigor
|
|
|
139
239
|
end
|
|
140
240
|
WriteResult.new(source_path: source_path, target_path: target,
|
|
141
241
|
action: :created, applied: candidates)
|
|
242
|
+
rescue SystemCallError => e
|
|
243
|
+
raise WriteError.new(target, e)
|
|
142
244
|
end
|
|
143
245
|
|
|
144
246
|
# ADR-14 gap-#3 follow-up (c): when one candidate's `class_name` is a strict prefix of another's, emit a
|
|
@@ -157,10 +259,11 @@ module Rigor
|
|
|
157
259
|
tree = build_namespace_tree(candidates, shells)
|
|
158
260
|
kinds = merged_namespace_kinds(candidates)
|
|
159
261
|
supers = merged_superclasses(candidates)
|
|
262
|
+
@type_params = merged_type_params(candidates)
|
|
160
263
|
render_tree_nodes(tree, kinds, supers, 0)
|
|
161
264
|
end
|
|
162
265
|
|
|
163
|
-
#
|
|
266
|
+
# Unions `class_shells` across every candidate; the generator's walker populates the same set on every
|
|
164
267
|
# candidate produced from a given file (gap-#3 (e)).
|
|
165
268
|
def collect_class_shells(candidates)
|
|
166
269
|
shells = Set.new
|
|
@@ -202,6 +305,13 @@ module Rigor
|
|
|
202
305
|
merged
|
|
203
306
|
end
|
|
204
307
|
|
|
308
|
+
# #2436 — every candidate carries the run's map, so folding the target's candidates is the whole view.
|
|
309
|
+
def merged_type_params(candidates)
|
|
310
|
+
candidates.each_with_object({}) do |c, acc|
|
|
311
|
+
acc.merge!(c.class_type_params) if c.respond_to?(:class_type_params)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
205
315
|
def build_superclasses(candidates)
|
|
206
316
|
candidates.each_with_object({}) do |candidate, acc|
|
|
207
317
|
next unless candidate.respond_to?(:class_superclasses)
|
|
@@ -257,7 +367,7 @@ module Rigor
|
|
|
257
367
|
indent = INDENT * depth
|
|
258
368
|
qualified = (prefix + [node[:name]]).join("::")
|
|
259
369
|
keyword = node_keyword(node, kinds, qualified)
|
|
260
|
-
header = "#{keyword} #{node[:name]}#{superclass_suffix(keyword, supers, qualified)}"
|
|
370
|
+
header = "#{keyword} #{node[:name]}#{@type_params[qualified]}#{superclass_suffix(keyword, supers, qualified)}"
|
|
261
371
|
body = render_tree_node_body(node, kinds, supers, depth, prefix)
|
|
262
372
|
"#{indent}#{header}\n#{body}#{indent}end\n"
|
|
263
373
|
end
|
|
@@ -293,13 +403,12 @@ module Rigor
|
|
|
293
403
|
:module
|
|
294
404
|
end
|
|
295
405
|
|
|
296
|
-
def update_existing(source_path, target, candidates)
|
|
406
|
+
def update_existing(source_path, target, candidates, repair_only: false)
|
|
297
407
|
source = target.read
|
|
298
408
|
# Pre-parser encoding guard, mirroring `Environment::RbsLoader.invalid_encoding?`: handed invalid
|
|
299
409
|
# UTF-8, `RBS::Parser` raises a bare `ArgumentError` on rbs 4.1 (not the `ParsingError` that
|
|
300
410
|
# `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
|
|
411
|
+
# supports. The refusal is loud — a mojibake file would otherwise fail with a stack trace naming neither
|
|
303
412
|
# the file nor the fix.
|
|
304
413
|
unless source.valid_encoding?
|
|
305
414
|
return WriteResult.new(source_path: source_path, target_path: target,
|
|
@@ -307,16 +416,19 @@ module Rigor
|
|
|
307
416
|
error: "#{target} is not valid UTF-8")
|
|
308
417
|
end
|
|
309
418
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
419
|
+
# An existing file that does not parse is refused like a mis-encoded one, not skipped as `:noop` (#2480):
|
|
420
|
+
# `--check` must fail on it, because a `--write` that cannot touch the file is not an up-to-date `sig/`.
|
|
421
|
+
# `rigor check` quarantines such a file whole; this refusal names it at the sig-gen boundary too.
|
|
422
|
+
decls, parse_detail = parse_target_signature(target, source)
|
|
423
|
+
if decls.nil?
|
|
424
|
+
return WriteResult.new(source_path: source_path, target_path: target,
|
|
425
|
+
action: :skipped_unparseable,
|
|
426
|
+
error: "#{target} does not parse as RBS: #{parse_detail}")
|
|
427
|
+
end
|
|
316
428
|
|
|
317
|
-
|
|
318
|
-
unless
|
|
319
|
-
return WriteResult.new(source_path: source_path, target_path: target, action:
|
|
429
|
+
state = merged_state(source, decls, candidates, repair_only, target)
|
|
430
|
+
unless state_changed?(state)
|
|
431
|
+
return WriteResult.new(source_path: source_path, target_path: target, action: :noop,
|
|
320
432
|
applied: state.applied, skipped: state.skipped,
|
|
321
433
|
left_unreadable: state.left_unreadable)
|
|
322
434
|
end
|
|
@@ -333,8 +445,11 @@ module Rigor
|
|
|
333
445
|
|
|
334
446
|
target.write(state.source) unless @dry_run
|
|
335
447
|
WriteResult.new(source_path: source_path, target_path: target,
|
|
336
|
-
action:
|
|
337
|
-
left_unreadable: state.left_unreadable, replaced: state.replaced
|
|
448
|
+
action: :updated, applied: state.applied, skipped: state.skipped,
|
|
449
|
+
left_unreadable: state.left_unreadable, replaced: state.replaced,
|
|
450
|
+
repaired_headers: state.repaired_headers)
|
|
451
|
+
rescue SystemCallError => e
|
|
452
|
+
raise WriteError.new(target, e)
|
|
338
453
|
end
|
|
339
454
|
|
|
340
455
|
# Applies every class group, then every requested shell, then normalises the layout the three steps
|
|
@@ -343,12 +458,89 @@ module Rigor
|
|
|
343
458
|
supers = merged_superclasses(candidates)
|
|
344
459
|
kinds = merged_namespace_kinds(candidates)
|
|
345
460
|
shells = collect_class_shells(candidates)
|
|
461
|
+
@type_params = merged_type_params(candidates)
|
|
346
462
|
groups = candidates.group_by(&:class_name)
|
|
347
463
|
# Ancestors first, so `Foo` is created (or found) before `Foo::Bar` looks for a parent to nest under.
|
|
348
464
|
# A plain sort suffices: a name always sorts before every name it is a strict prefix of.
|
|
349
465
|
groups.keys.sort.each { |name| merge_class(state, name, groups.fetch(name), kinds, supers) }
|
|
350
466
|
merge_class_shells(state, shells, kinds, supers)
|
|
351
467
|
collapse_nested_declarations(state, groups.keys + shells.to_a)
|
|
468
|
+
# Last, so the members this run merged count towards {#shadowed_after_merge?}.
|
|
469
|
+
repair_generic_headers(state, state.target)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# Issue #2580 — a declaration this file already holds for a class the loaded RBS declares generic, opened
|
|
473
|
+
# with no type parameters at all (`class Array`, as sig-gen wrote it before #2436), is a guaranteed
|
|
474
|
+
# `RBS::GenericParameterMismatchError`: rbs requires every declaration of a class to repeat the first one's
|
|
475
|
+
# parameters, and the class, plus every class whose ancestry reaches it (`Object` through `Kernel`), then
|
|
476
|
+
# fails its definition build. Writing more members into that header would keep the file broken, so the
|
|
477
|
+
# header gets the same spelling a new header would ({Generator#stamp_generic_reopenings}: plain
|
|
478
|
+
# parameters only). A header that spells parameters of its own is the project's and is left alone, as is
|
|
479
|
+
# every class neither the run's stamp nor {#write_all}'s scan names.
|
|
480
|
+
#
|
|
481
|
+
# A declaration whose body names a type like one of the parameters (`def old: () -> E` for a project class
|
|
482
|
+
# `E`) is left bare: under `class Array[unchecked out E]` that `E` would bind to the parameter, silently
|
|
483
|
+
# changing what the member says. The body is matched as text, so a comment or a nested declaration that
|
|
484
|
+
# spells the name also keeps the header bare, which is the state the file was already in; a method type that
|
|
485
|
+
# declares the name as its own parameter (`def m: [E] (E) -> E`) does not (#2605).
|
|
486
|
+
#
|
|
487
|
+
# Under {#write_all} the class is repaired in every file under the sig root or in none ({GenericHeaderScan},
|
|
488
|
+
# #2605): a class held back there is not repaired here, and a class the run does not stamp is repaired with the
|
|
489
|
+
# scan's spelling (#2603). The check below runs again after the merge, so a member this run merged counts
|
|
490
|
+
# too; the generator anchors a member's type that a parameter would capture (#2436), so it holds back a class
|
|
491
|
+
# the scan cleared only for a member written without that anchor, and is then reported in {#left_bare}.
|
|
492
|
+
# `repaired_headers` gets one entry per declaration repaired.
|
|
493
|
+
def repair_generic_headers(state, target)
|
|
494
|
+
spellings = @scan_repairs.merge(@type_params).except(*@held_back)
|
|
495
|
+
spellings.keys.sort.each do |name|
|
|
496
|
+
spelling = spellings.fetch(name)
|
|
497
|
+
bare = find_class_decls(state.decls, name).select { |d| d.type_params.empty? && d.location&.[](:name) }
|
|
498
|
+
next if bare.empty? || shadowed_after_merge?(state.source, bare, name, spelling, target)
|
|
499
|
+
|
|
500
|
+
insert_type_params(state, bare, spelling)
|
|
501
|
+
bare.each { state.repaired_headers << "#{name}#{spelling}" }
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Whether any of `bare` names one of the parameters `spelling` declares ({GenericHeaderScan.shadowing_names});
|
|
506
|
+
# each such opening is recorded in {#left_bare}.
|
|
507
|
+
def shadowed_after_merge?(source, bare, name, spelling, target)
|
|
508
|
+
params = GenericHeaderScan.parameter_names(spelling)
|
|
509
|
+
return true if params.nil?
|
|
510
|
+
|
|
511
|
+
blocking = bare.filter_map do |decl|
|
|
512
|
+
names = GenericHeaderScan.shadowing_names(source, decl, params)
|
|
513
|
+
{ target: target.to_s, reason: "names_parameter", names: names } unless names.empty?
|
|
514
|
+
end
|
|
515
|
+
return false if blocking.empty?
|
|
516
|
+
|
|
517
|
+
@left_bare << { class: name, targets: [target.to_s], blocking: blocking }
|
|
518
|
+
true
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Splices `spelling` right after each declaration's name, last one first so earlier offsets stay valid.
|
|
522
|
+
def insert_type_params(state, decls, spelling)
|
|
523
|
+
decls.map { |d| d.location[:name].end_pos }.sort.reverse_each do |pos|
|
|
524
|
+
state.source = state.source[0...pos] + spelling + state.source[pos..]
|
|
525
|
+
end
|
|
526
|
+
state.decls = parse_signature(state.source) || state.decls
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def merged_state(source, decls, candidates, repair_only, target)
|
|
530
|
+
state = MergeState.new(source: source, decls: decls, applied: [], skipped: [], left_unreadable: [],
|
|
531
|
+
replaced: [], repaired_headers: [], target: target)
|
|
532
|
+
repair_only ? repair_only_candidates(state, candidates) : merge_candidates(state, candidates)
|
|
533
|
+
state
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def state_changed?(state)
|
|
537
|
+
!(state.applied.empty? && state.repaired_headers.empty?)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# {#repair_only_results}' merge step: the run's stamp, and the header repair alone.
|
|
541
|
+
def repair_only_candidates(state, candidates)
|
|
542
|
+
@type_params = merged_type_params(candidates)
|
|
543
|
+
repair_generic_headers(state, state.target)
|
|
352
544
|
end
|
|
353
545
|
|
|
354
546
|
# ADR-14 gap-#3 (e): for every requested class shell that isn't already declared in the target file,
|
|
@@ -401,7 +593,9 @@ module Rigor
|
|
|
401
593
|
parent_name = segments[0...-1].join("::")
|
|
402
594
|
parent = find_class_decl(state.decls, parent_name)
|
|
403
595
|
decl = parent && find_class_decl(state.decls, qualified)
|
|
404
|
-
|
|
596
|
+
# A rooted declaration (`module A; class ::B::C`) keeps the layout the user wrote: cutting it out of its
|
|
597
|
+
# enclosing module would leave that module empty, so the member merges in place instead.
|
|
598
|
+
return if decl.nil? || overlapping?(parent, decl) || decl.name.to_s.start_with?("::")
|
|
405
599
|
|
|
406
600
|
region = decl_region(state.source, decl)
|
|
407
601
|
block = region && relocated_block(state.source, decl, parent, segments.last, region)
|
|
@@ -560,6 +754,15 @@ module Rigor
|
|
|
560
754
|
nil
|
|
561
755
|
end
|
|
562
756
|
|
|
757
|
+
# Parses an EXISTING target, naming it as the buffer so the parse error carries `path:line:col` (a bare
|
|
758
|
+
# String would be named `a.rbs`). Returns `[decls, nil]`, or `[nil, first_line_of_message]` on a parse error.
|
|
759
|
+
def parse_target_signature(target, source)
|
|
760
|
+
_, _, decls = RBS::Parser.parse_signature(RBS::Buffer.new(name: target.to_s, content: source))
|
|
761
|
+
[decls, nil]
|
|
762
|
+
rescue RBS::ParsingError => e
|
|
763
|
+
[nil, e.message.to_s.lines.first.to_s.strip]
|
|
764
|
+
end
|
|
765
|
+
|
|
563
766
|
def merge_class(state, class_name, methods, kinds, supers)
|
|
564
767
|
decl = find_class_decl(state.decls, class_name)
|
|
565
768
|
if decl.nil?
|
|
@@ -586,11 +789,10 @@ module Rigor
|
|
|
586
789
|
decls.each do |decl|
|
|
587
790
|
next unless decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)
|
|
588
791
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return decl if full == qualified_name
|
|
792
|
+
path = ::Rigor::Environment::RbsLoader.declared_class_path(decl, prefix)
|
|
793
|
+
return decl if path.join("::") == qualified_name
|
|
592
794
|
|
|
593
|
-
nested = find_class_decl_in(decl.members,
|
|
795
|
+
nested = find_class_decl_in(decl.members, path, qualified_name)
|
|
594
796
|
return nested if nested
|
|
595
797
|
end
|
|
596
798
|
nil
|
|
@@ -602,10 +804,9 @@ module Rigor
|
|
|
602
804
|
decls.each do |decl|
|
|
603
805
|
next unless decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)
|
|
604
806
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
find_class_decls(decl.members, qualified_name, prefix + [local], found)
|
|
807
|
+
path = ::Rigor::Environment::RbsLoader.declared_class_path(decl, prefix)
|
|
808
|
+
found << decl if path.join("::") == qualified_name
|
|
809
|
+
find_class_decls(decl.members, qualified_name, path, found)
|
|
609
810
|
end
|
|
610
811
|
found
|
|
611
812
|
end
|
|
@@ -669,7 +870,7 @@ module Rigor
|
|
|
669
870
|
# name, slices out the old declaration's source range and substitutes the new RBS one-liner. Members that
|
|
670
871
|
# are not `MethodDefinition`s are left alone.
|
|
671
872
|
#
|
|
672
|
-
#
|
|
873
|
+
# Under `--overwrite`, an inline overwrite is always eligible, and so are two other classifications:
|
|
673
874
|
#
|
|
674
875
|
# 1. `TIGHTER_RETURN` — the classifier already proved the new return type is a strict subtype of the
|
|
675
876
|
# declared one (with lenience guards passed).
|
data/lib/rigor/sig_gen.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "sig_gen/classification"
|
|
|
5
5
|
require_relative "sig_gen/effect_annotation"
|
|
6
6
|
require_relative "sig_gen/inline_declarations"
|
|
7
7
|
require_relative "sig_gen/method_candidate"
|
|
8
|
+
require_relative "sig_gen/platform_guarded_files"
|
|
8
9
|
require_relative "sig_gen/observed_call"
|
|
9
10
|
require_relative "sig_gen/meta_class_shape"
|
|
10
11
|
require_relative "sig_gen/type_elaborator"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require_relative "literals"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Source
|
|
8
|
+
# Issue #2793 — the methods a `Forwardable` / `SingleForwardable` delegator macro defines, read off its syntax
|
|
9
|
+
# alone. Ruby 4.0's `forwardable.rb` is the oracle for each row:
|
|
10
|
+
#
|
|
11
|
+
# - `def_instance_delegator(acc, m, ali = m)` defines `ali` on the instance side;
|
|
12
|
+
# - `def_instance_delegators(acc, *ms)` defines each `m` but `__send__` and `__id__`, instance side (the two are
|
|
13
|
+
# recorded anyway: `BasicObject` declares both, so a recorded entry changes no answer);
|
|
14
|
+
# - `instance_delegate(ms => acc)` defines each key name (a name or an array of them), instance side;
|
|
15
|
+
# - `def_single_delegator(s)` and `single_delegate` are the same on the singleton side;
|
|
16
|
+
# - `def_delegator(s)` and `delegate` are the same on the side the `extend` decides.
|
|
17
|
+
#
|
|
18
|
+
# `def_delegator(s)` and `delegate` are `Forwardable`'s instance aliases and `SingleForwardable`'s singleton
|
|
19
|
+
# ones, so their side is {AMBIGUOUS} and the caller settles it from the `extend` it has seen. `def_delegator :@x,
|
|
20
|
+
# :last, :final` defines `final` and never `last`.
|
|
21
|
+
#
|
|
22
|
+
# A name the syntax does not spell (`def_delegators :@x, *NAMES`, `delegate names => :@x`) makes the delegation
|
|
23
|
+
# {Delegation#open?}: the macro defines methods no reader can enumerate. `delegate` takes Forwardable's
|
|
24
|
+
# single-hash form only, so ActiveSupport's `delegate :a, to: :b` (positional names before a `to:` key) is
|
|
25
|
+
# not read here.
|
|
26
|
+
module ForwardableDelegators
|
|
27
|
+
AMBIGUOUS = :ambiguous
|
|
28
|
+
|
|
29
|
+
# The names a delegator macro defines, on which side, and whether it also defines names its syntax hides.
|
|
30
|
+
Delegation = Data.define(:side, :names, :open) do
|
|
31
|
+
def open? = open
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# `[side, shape]` per macro: `:alias` takes `(accessor, method, alias = method)`, `:list` takes `(accessor,
|
|
35
|
+
# *methods)`, `:hash` takes `(methods => accessor, …)`.
|
|
36
|
+
MACROS = Ractor.make_shareable(
|
|
37
|
+
{
|
|
38
|
+
def_instance_delegator: %i[instance alias], def_instance_delegators: %i[instance list],
|
|
39
|
+
instance_delegate: %i[instance hash],
|
|
40
|
+
def_single_delegator: %i[singleton alias], def_single_delegators: %i[singleton list],
|
|
41
|
+
single_delegate: %i[singleton hash],
|
|
42
|
+
def_delegator: [AMBIGUOUS, :alias], def_delegators: [AMBIGUOUS, :list], delegate: [AMBIGUOUS, :hash]
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
private_constant :MACROS
|
|
47
|
+
|
|
48
|
+
module_function
|
|
49
|
+
|
|
50
|
+
# The {Delegation} of an implicit-self (or `self.`) delegator macro call, or nil for any other call, and for
|
|
51
|
+
# one whose arguments are not a shape the macro accepts.
|
|
52
|
+
def of(call_node)
|
|
53
|
+
return nil unless call_node.is_a?(Prism::CallNode)
|
|
54
|
+
return nil unless call_node.receiver.nil? || call_node.receiver.is_a?(Prism::SelfNode)
|
|
55
|
+
|
|
56
|
+
side, shape = MACROS[call_node.name]
|
|
57
|
+
return nil if side.nil?
|
|
58
|
+
|
|
59
|
+
arguments = call_node.arguments&.arguments || []
|
|
60
|
+
return Delegation.new(side: side, names: [].freeze, open: true) if unplaceable_splat?(shape, arguments)
|
|
61
|
+
|
|
62
|
+
names, open = case shape
|
|
63
|
+
when :alias then alias_names(arguments)
|
|
64
|
+
when :list then list_names(arguments)
|
|
65
|
+
else hash_names(arguments)
|
|
66
|
+
end
|
|
67
|
+
return nil if names.nil?
|
|
68
|
+
|
|
69
|
+
Delegation.new(side: side, names: names.freeze, open: open)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A splat (`def_delegator(*ARGS)`, `def_delegators(*ARGS)`, `delegate(*pairs)`) hides which argument is the
|
|
73
|
+
# accessor and which a name, so the macro may define any name. A splat after the accessor of the list form is
|
|
74
|
+
# only a hidden name, and {#list_names} reads the literal names beside it.
|
|
75
|
+
def unplaceable_splat?(shape, arguments)
|
|
76
|
+
splats = arguments.each_index.select { |index| arguments[index].is_a?(Prism::SplatNode) }
|
|
77
|
+
return false if splats.empty?
|
|
78
|
+
|
|
79
|
+
shape != :list || splats.include?(0)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def alias_names(arguments)
|
|
83
|
+
return nil unless (2..3).cover?(arguments.size)
|
|
84
|
+
|
|
85
|
+
name = Literals.symbol_or_string(arguments.last)
|
|
86
|
+
name.nil? ? [[], true] : [[name], false]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def list_names(arguments)
|
|
90
|
+
return nil if arguments.empty?
|
|
91
|
+
|
|
92
|
+
rest = arguments.drop(1)
|
|
93
|
+
names = rest.filter_map { |argument| Literals.symbol_or_string(argument) }
|
|
94
|
+
[names, names.size != rest.size]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def hash_names(arguments)
|
|
98
|
+
return nil unless arguments.size == 1
|
|
99
|
+
|
|
100
|
+
hash = arguments.first
|
|
101
|
+
return nil unless hash.is_a?(Prism::KeywordHashNode) || hash.is_a?(Prism::HashNode)
|
|
102
|
+
|
|
103
|
+
names = []
|
|
104
|
+
open = false
|
|
105
|
+
hash.elements.each do |element|
|
|
106
|
+
return [[], true] unless element.is_a?(Prism::AssocNode)
|
|
107
|
+
|
|
108
|
+
keys = key_names(element.key)
|
|
109
|
+
keys.nil? ? (open = true) : names.concat(keys)
|
|
110
|
+
end
|
|
111
|
+
[names, open]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# The names a hash key spells — a literal name, or an array of them — or nil when any is computed.
|
|
115
|
+
def key_names(key)
|
|
116
|
+
elements = key.is_a?(Prism::ArrayNode) ? key.elements : [key]
|
|
117
|
+
names = elements.map { |element| Literals.symbol_or_string(element) }
|
|
118
|
+
names.include?(nil) ? nil : names
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -109,6 +109,19 @@ module Rigor
|
|
|
109
109
|
def each_child(node, &)
|
|
110
110
|
node.rigor_each_child(&) if node.is_a?(Prism::Node)
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
# #2455 — whether `root` nests deeper than `bound` levels, measured without recursion (the question is asked after
|
|
114
|
+
# the recursive walkers ran out of stack). Stops as soon as one path passes the bound.
|
|
115
|
+
def self.nested_deeper_than?(root, bound)
|
|
116
|
+
stack = [[root, 1]]
|
|
117
|
+
until stack.empty?
|
|
118
|
+
node, depth = stack.pop
|
|
119
|
+
return true if depth > bound
|
|
120
|
+
|
|
121
|
+
node.rigor_each_child { |child| stack << [child, depth + 1] }
|
|
122
|
+
end
|
|
123
|
+
false
|
|
124
|
+
end
|
|
112
125
|
end
|
|
113
126
|
end
|
|
114
127
|
end
|
|
@@ -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`
|