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
|
@@ -57,12 +57,13 @@ module Rigor
|
|
|
57
57
|
last: :tuple_last,
|
|
58
58
|
size: :tuple_size,
|
|
59
59
|
length: :tuple_size,
|
|
60
|
-
count: :
|
|
60
|
+
count: :tuple_count,
|
|
61
61
|
empty?: :tuple_empty?,
|
|
62
62
|
any?: :tuple_any?,
|
|
63
63
|
all?: :tuple_all?,
|
|
64
64
|
none?: :tuple_none?,
|
|
65
65
|
include?: :tuple_include?,
|
|
66
|
+
member?: :tuple_include?,
|
|
66
67
|
sum: :tuple_sum,
|
|
67
68
|
min: :tuple_min,
|
|
68
69
|
max: :tuple_max,
|
|
@@ -70,13 +71,23 @@ module Rigor
|
|
|
70
71
|
sort: :tuple_sort,
|
|
71
72
|
reverse: :tuple_reverse,
|
|
72
73
|
to_a: :tuple_to_a,
|
|
74
|
+
entries: :tuple_to_a,
|
|
75
|
+
to_ary: :tuple_to_a,
|
|
73
76
|
to_h: :tuple_to_h,
|
|
77
|
+
to_set: :tuple_to_set,
|
|
74
78
|
zip: :tuple_zip,
|
|
75
79
|
:[] => :tuple_index,
|
|
76
80
|
slice: :tuple_index,
|
|
77
81
|
fetch: :tuple_index,
|
|
78
82
|
dig: :tuple_dig,
|
|
79
83
|
values_at: :tuple_values_at,
|
|
84
|
+
fetch_values: :tuple_fetch_values,
|
|
85
|
+
assoc: :tuple_assoc,
|
|
86
|
+
rassoc: :tuple_rassoc,
|
|
87
|
+
tally: :tuple_tally,
|
|
88
|
+
transpose: :tuple_transpose,
|
|
89
|
+
product: :tuple_product,
|
|
90
|
+
pack: :tuple_pack,
|
|
80
91
|
# `concat` / `<<` are deliberately ABSENT from this table: both mutate the receiver in place
|
|
81
92
|
# (`ARRAY_MUTATORS` in `MutationWidening`), and a fold tier has no way to write the folded
|
|
82
93
|
# result back into the caller's binding — it can only return a value for the call expression.
|
|
@@ -194,6 +205,7 @@ module Rigor
|
|
|
194
205
|
Type::Tuple => :dispatch_tuple,
|
|
195
206
|
Type::HashShape => :dispatch_hash_shape,
|
|
196
207
|
Type::Nominal => :dispatch_nominal_size,
|
|
208
|
+
Type::Constant => :dispatch_constant_set,
|
|
197
209
|
Type::Difference => :dispatch_difference,
|
|
198
210
|
Type::Refined => :dispatch_refined,
|
|
199
211
|
Type::Intersection => :dispatch_intersection,
|
|
@@ -215,6 +227,7 @@ module Rigor
|
|
|
215
227
|
# `CONST = [...].freeze; CONST.any? { … }`; fixing it at the fold (not the rule) unblocks that
|
|
216
228
|
# preservation.
|
|
217
229
|
return nil unless context.block_type.nil?
|
|
230
|
+
return constant_set_first(context) if method_name == :first && receiver.is_a?(Type::Constant)
|
|
218
231
|
|
|
219
232
|
handler = RECEIVER_HANDLERS[receiver.class]
|
|
220
233
|
return nil unless handler
|
|
@@ -302,9 +315,62 @@ module Rigor
|
|
|
302
315
|
when :flatten then array_nominal_flatten(nominal, args)
|
|
303
316
|
when :compact then array_nominal_compact(nominal, args)
|
|
304
317
|
end
|
|
318
|
+
when "Set"
|
|
319
|
+
set_nominal_algebra(nominal, method_name, args)
|
|
305
320
|
end
|
|
306
321
|
end
|
|
307
322
|
|
|
323
|
+
# The Set algebra that returns a new Set holding both operands' elements (issue #2294). RBS declares each
|
|
324
|
+
# as `(_Each[A]) -> Set[A]`, the receiver's element alone, so `ARGV.to_set | [1]` read `Set[String]` and a
|
|
325
|
+
# read of its `1` reported on correct code. The result is `Set[E | F]`, `F` the argument's elements with
|
|
326
|
+
# their value pins dropped ({SetMutation.element_types}).
|
|
327
|
+
SET_UNION_ALGEBRA = %i[| union + ^].to_set.freeze
|
|
328
|
+
private_constant :SET_UNION_ALGEBRA
|
|
329
|
+
|
|
330
|
+
def set_nominal_algebra(nominal, method_name, args)
|
|
331
|
+
return nil unless SET_UNION_ALGEBRA.include?(method_name) && args.size == 1
|
|
332
|
+
|
|
333
|
+
receiver = nominal.type_args.size == 1 ? nominal.type_args.first : Type::Combinator.untyped
|
|
334
|
+
added = Inference::SetMutation.element_types(args.first).map { |t| MethodDispatcher.array_new_element(t) }
|
|
335
|
+
Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.union(receiver, *added)])
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# The same algebra on a constant-folded Set whose argument is not constant (two constants fold upstream in
|
|
339
|
+
# {ConstantFolding}): the receiver's members and the argument's elements, typed as #2290 types a
|
|
340
|
+
# `Set[...]` literal over non-constant members ({SetFolding.typed_set}).
|
|
341
|
+
def dispatch_constant_set(constant, method_name, args)
|
|
342
|
+
return nil unless SET_UNION_ALGEBRA.include?(method_name) && args.size == 1
|
|
343
|
+
return nil unless Inference::SetMutation.constant?(constant)
|
|
344
|
+
|
|
345
|
+
members = constant.value.map { |v| Type::Combinator.constant_of(v) }
|
|
346
|
+
SetFolding.typed_set(members + Inference::SetMutation.element_types(args.first)) ||
|
|
347
|
+
Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.untyped])
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Issue #2657 — `Set#first` (no argument) on a constant-folded Set held in a LOCAL that provably nobody else
|
|
351
|
+
# can reach. RBS alone answers `E?` over a constant receiver whose `E` is unbound, so `Set["r"].first` read
|
|
352
|
+
# `Dynamic[top]?`. The answer is the literal's first member (insertion order), `nil` for the empty set.
|
|
353
|
+
#
|
|
354
|
+
# What is proved: the receiver is a bare local read whose binding carries the `:synthesised` construction
|
|
355
|
+
# fact, which {Inference::ConstructionEscape} withholds from a local that is referenced inside any block or
|
|
356
|
+
# lambda, passed as an argument, held in a container or an alias, receives a call outside its reader /
|
|
357
|
+
# iterator / modelled-mutator lists (`method`, `instance_eval`, `send`, a project-defined name), or lives in a
|
|
358
|
+
# body that calls `binding` / `eval`; and the modelled mutators (`clear`, `delete`, `<<`, ...) widen the
|
|
359
|
+
# carrier off `Constant[Set]` at the call itself.
|
|
360
|
+
# What is NOT covered, and keeps the RBS answer: a receiver that is not a local (an ivar, a global, a
|
|
361
|
+
# constant, a call result, a literal), and whatever the escape scan itself does not see.
|
|
362
|
+
def constant_set_first(context)
|
|
363
|
+
constant = context.receiver
|
|
364
|
+
return nil unless context.args.nil? || context.args.empty?
|
|
365
|
+
return nil unless Inference::SetMutation.constant?(constant)
|
|
366
|
+
|
|
367
|
+
node = context.call_node&.receiver
|
|
368
|
+
return nil unless node.is_a?(Prism::LocalVariableReadNode)
|
|
369
|
+
return nil unless context.scope&.construction_fact?(node.name)
|
|
370
|
+
|
|
371
|
+
Type::Combinator.constant_of(constant.value.first)
|
|
372
|
+
end
|
|
373
|
+
|
|
308
374
|
# `Integer#to_s` / `Integer#inspect` (#993) on a bare `Nominal[Integer]` receiver, ahead of the
|
|
309
375
|
# existing `:*` binary fold. `#inspect` takes no argument and matches `#to_s()` byte for byte, so
|
|
310
376
|
# it routes through the same no-arg branch.
|
|
@@ -317,7 +383,7 @@ module Rigor
|
|
|
317
383
|
end
|
|
318
384
|
|
|
319
385
|
# `decimal-int-string`'s predicate (`/\A-?\d+\z/`) admits the optional sign, so — unlike the
|
|
320
|
-
# `IntegerRange#to_s` tier
|
|
386
|
+
# `IntegerRange#to_s` tier below, which declines a signed range because it cannot yet prove the
|
|
321
387
|
# sign is absent — a bare `Integer` (sign completely unknown) still reaches it for the base-10
|
|
322
388
|
# case: base 10 is Ruby's default sign-and-digits grammar for every Integer. Any other base,
|
|
323
389
|
# known or not statically known, drops to `non-empty-string`: `to_s(base)` never returns `""`,
|
|
@@ -422,14 +488,29 @@ module Rigor
|
|
|
422
488
|
# Resolves the element type of a flattened `Array[element]`. Each `Array[U]` nesting level
|
|
423
489
|
# contributes `U`; the per-level element types are unioned. `depth < 0` recurses without bound;
|
|
424
490
|
# `depth == 0` stops (Ruby's `flatten(0)` is a no-op copy and returns the element unchanged).
|
|
491
|
+
#
|
|
492
|
+
# Issue #2427 — a `Tuple` element is an Array too and contributes the union of its own elements, each
|
|
493
|
+
# flattened one level deeper, and so does a `Tuple` member of a `Union` element. `ARGV.map { |x| [x] }`
|
|
494
|
+
# is `Array[[String]]`, and returning its `[String]` element unchanged made `.flatten.first.upcase`
|
|
495
|
+
# report `undefined method 'upcase' for [String]` on code Ruby runs.
|
|
496
|
+
#
|
|
497
|
+
# A nominal `Array[U]` member of a `Union` element is kept. RBS's `String#scan(Regexp)` answers
|
|
498
|
+
# `Array[String | Array[String?]]`, the `?` standing for an optional group, and flattening that member
|
|
499
|
+
# turned the idiomatic `str.scan(/k = (.+)/).flatten.map(&:strip)` into a `possible-nil-receiver` for a
|
|
500
|
+
# group that always matches.
|
|
425
501
|
def flatten_nominal_element(element, depth)
|
|
426
502
|
return element if depth.zero?
|
|
427
|
-
return element unless array_nominal?(element)
|
|
428
|
-
|
|
429
|
-
inner = element.type_args.first
|
|
430
|
-
return element if inner.nil?
|
|
431
503
|
|
|
432
|
-
|
|
504
|
+
case element
|
|
505
|
+
when Type::Tuple
|
|
506
|
+
Type::Combinator.union(*element.elements.map { |inner| flatten_nominal_element(inner, depth - 1) })
|
|
507
|
+
when Type::Union
|
|
508
|
+
Type::Combinator.union(*element.members.map do |member|
|
|
509
|
+
member.is_a?(Type::Tuple) ? flatten_nominal_element(member, depth) : member
|
|
510
|
+
end)
|
|
511
|
+
else
|
|
512
|
+
array_nominal?(element) ? flatten_nominal_element(element.type_args.first, depth - 1) : element
|
|
513
|
+
end
|
|
433
514
|
end
|
|
434
515
|
|
|
435
516
|
def array_nominal?(type)
|
|
@@ -472,7 +553,7 @@ module Rigor
|
|
|
472
553
|
# leading sign. Base 10 is Ruby's decimal-literal grammar, so the result lifts to
|
|
473
554
|
# `decimal-int-string`. Signed ranges fall through entirely (the result could carry a `-` sign
|
|
474
555
|
# that `decimal-int-string` alone would need a sign proof for, matching the
|
|
475
|
-
# `Nominal[Integer]` tier's own signed-base-10 carve-out
|
|
556
|
+
# `Nominal[Integer]` tier's own signed-base-10 carve-out above).
|
|
476
557
|
#
|
|
477
558
|
# #1004 — every other base (a different literal, such as 8 or 16, or a value that is not
|
|
478
559
|
# statically known) does NOT lift to `octal-int-string` / `hex-int-string`: those refinements'
|
|
@@ -621,10 +702,10 @@ module Rigor
|
|
|
621
702
|
Type::Combinator.positive_int
|
|
622
703
|
end
|
|
623
704
|
|
|
624
|
-
# Predicate-subset projections over a `Refined[base, predicate]` receiver.
|
|
625
|
-
# the String case-normalisation pair: `s.downcase` over a
|
|
626
|
-
# the same carrier (already lowercase), and `s.upcase` lifts a
|
|
627
|
-
# `uppercase-string`. Symmetric rules apply with the predicates swapped. Numeric-string
|
|
705
|
+
# Predicate-subset projections over a `Refined[base, predicate]` receiver. The catalogue is
|
|
706
|
+
# `REFINED_STRING_PROJECTIONS`, led by the String case-normalisation pair: `s.downcase` over a
|
|
707
|
+
# `lowercase-string` receiver folds to the same carrier (already lowercase), and `s.upcase` lifts a
|
|
708
|
+
# `lowercase-string` to `uppercase-string`. Symmetric rules apply with the predicates swapped. Numeric-string
|
|
628
709
|
# idempotence over `#downcase` / `#upcase` is also recognised because a numeric string equals its
|
|
629
710
|
# own case-normalisation.
|
|
630
711
|
#
|
|
@@ -747,7 +828,7 @@ module Rigor
|
|
|
747
828
|
# Compute the bounded-integer meet of two or more `IntegerRange` carriers. We compare via the
|
|
748
829
|
# numeric `lower` / `upper` accessors (`-Float::INFINITY` / `Float::INFINITY` for the symbolic
|
|
749
830
|
# ends), then map back to the symbolic-bound representation `IntegerRange.new` expects. The
|
|
750
|
-
# disjoint-meet case cannot arise from sound member-wise projections
|
|
831
|
+
# disjoint-meet case cannot arise from sound member-wise projections but is guarded
|
|
751
832
|
# defensively to keep the carrier total.
|
|
752
833
|
def narrow_integer_ranges(ranges)
|
|
753
834
|
numeric_low = ranges.map(&:lower).max
|
|
@@ -798,6 +879,25 @@ module Rigor
|
|
|
798
879
|
Type::Combinator.constant_of(tuple.elements.size)
|
|
799
880
|
end
|
|
800
881
|
|
|
882
|
+
# `tuple.count` / `tuple.count(needle)` — 0-arg mirrors `#size`; 1-arg counts elements equal to
|
|
883
|
+
# `needle` (`==`) when the needle and every element are constant values or shapes (or folds to `0`
|
|
884
|
+
# on `[]`).
|
|
885
|
+
def tuple_count(tuple, _method_name, args)
|
|
886
|
+
return Type::Combinator.constant_of(tuple.elements.size) if args.empty?
|
|
887
|
+
return nil unless args.size == 1
|
|
888
|
+
|
|
889
|
+
needle = unwrap_constant_type(args.first)
|
|
890
|
+
return nil if needle.equal?(NOT_CONSTANT)
|
|
891
|
+
return Type::Combinator.constant_of(0) if tuple.elements.empty?
|
|
892
|
+
|
|
893
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
894
|
+
return nil if values.nil?
|
|
895
|
+
|
|
896
|
+
Type::Combinator.constant_of(values.count { |v| v == needle })
|
|
897
|
+
rescue StandardError
|
|
898
|
+
nil
|
|
899
|
+
end
|
|
900
|
+
|
|
801
901
|
# `tuple.empty?` — folds to a precise bool from the tuple's known arity.
|
|
802
902
|
# rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
|
803
903
|
def tuple_empty?(tuple, _method_name, args)
|
|
@@ -839,27 +939,29 @@ module Rigor
|
|
|
839
939
|
Type::Combinator.constant_of(decision)
|
|
840
940
|
end
|
|
841
941
|
|
|
842
|
-
# `tuple.include?(needle)` — folds to a precise bool when the needle
|
|
843
|
-
#
|
|
844
|
-
#
|
|
845
|
-
#
|
|
942
|
+
# `tuple.include?(needle)` — folds to a precise bool when the needle and elements are constant
|
|
943
|
+
# scalars or constant nested Tuple / HashShape values. If any element matches the needle's value
|
|
944
|
+
# the answer is `Constant[true]`; if every element is constant and structurally distinct from the
|
|
945
|
+
# needle the answer is `Constant[false]`.
|
|
846
946
|
def tuple_include?(tuple, _method_name, args)
|
|
847
947
|
return nil unless args.size == 1
|
|
848
948
|
|
|
849
|
-
needle = args.first
|
|
850
|
-
return nil
|
|
949
|
+
needle = unwrap_constant_type(args.first)
|
|
950
|
+
return nil if needle.equal?(NOT_CONSTANT)
|
|
851
951
|
return Type::Combinator.constant_of(false) if tuple.elements.empty?
|
|
852
952
|
|
|
853
|
-
return Type::Combinator.constant_of(true) if any_element_matches?(tuple.elements, needle
|
|
854
|
-
return Type::Combinator.constant_of(false)
|
|
953
|
+
return Type::Combinator.constant_of(true) if any_element_matches?(tuple.elements, needle)
|
|
954
|
+
return Type::Combinator.constant_of(false) unless unwrap_constant_elements(tuple.elements).nil?
|
|
855
955
|
|
|
856
956
|
nil
|
|
857
957
|
end
|
|
858
958
|
# rubocop:enable Style/ReturnNilInPredicateMethodDefinition
|
|
859
959
|
|
|
860
|
-
# `tuple.sum` — when every element is a numeric Constant
|
|
861
|
-
#
|
|
960
|
+
# `tuple.sum` / `tuple.sum(init)` — when every element is a numeric Constant (and optional `init`
|
|
961
|
+
# is a numeric Constant), fold to `Constant[sum]`; when every element and `init` are String
|
|
962
|
+
# Constants, fold to `Constant[sum]` within `TUPLE_JOIN_BYTE_LIMIT`.
|
|
862
963
|
def tuple_sum(tuple, _method_name, args)
|
|
964
|
+
return tuple_sum_with_init(tuple, args.first) if args.size == 1
|
|
863
965
|
return nil unless args.empty?
|
|
864
966
|
return Type::Combinator.constant_of(0) if tuple.elements.empty?
|
|
865
967
|
|
|
@@ -867,16 +969,45 @@ module Rigor
|
|
|
867
969
|
return nil if values.nil?
|
|
868
970
|
|
|
869
971
|
Type::Combinator.constant_of(values.sum)
|
|
972
|
+
rescue StandardError
|
|
973
|
+
nil
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
def tuple_sum_with_init(tuple, init)
|
|
977
|
+
return nil unless init.is_a?(Type::Constant)
|
|
978
|
+
return tuple_sum_with_string_init(tuple, init.value) if init.value.is_a?(String)
|
|
979
|
+
return nil unless init.value.is_a?(Numeric)
|
|
980
|
+
return init if tuple.elements.empty?
|
|
981
|
+
|
|
982
|
+
values = constant_numeric_values(tuple.elements)
|
|
983
|
+
return nil if values.nil?
|
|
984
|
+
|
|
985
|
+
Type::Combinator.constant_of(values.sum(init.value))
|
|
986
|
+
rescue StandardError
|
|
987
|
+
nil
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
def tuple_sum_with_string_init(tuple, init_str)
|
|
991
|
+
values = constant_values(tuple.elements)
|
|
992
|
+
return nil if values.nil? || !values.all?(String)
|
|
993
|
+
return nil if values.sum(&:bytesize) + init_str.bytesize > TUPLE_JOIN_BYTE_LIMIT
|
|
994
|
+
|
|
995
|
+
result = values.sum(init_str)
|
|
996
|
+
return nil if result.bytesize > TUPLE_JOIN_BYTE_LIMIT
|
|
997
|
+
|
|
998
|
+
Type::Combinator.constant_of(result)
|
|
999
|
+
rescue StandardError
|
|
1000
|
+
nil
|
|
870
1001
|
end
|
|
871
1002
|
|
|
872
1003
|
# `tuple.join(sep = "")` — fold to the joined `Constant[String]` when every element is a
|
|
873
|
-
#
|
|
874
|
-
#
|
|
1004
|
+
# constant scalar or nested constant Tuple and the separator is absent or a `Constant[String]`.
|
|
1005
|
+
# Capped at `TUPLE_JOIN_BYTE_LIMIT`.
|
|
875
1006
|
def tuple_join(tuple, _method_name, args)
|
|
876
1007
|
sep = tuple_join_separator(args)
|
|
877
1008
|
return nil if sep.nil?
|
|
878
1009
|
|
|
879
|
-
values =
|
|
1010
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
880
1011
|
return nil if values.nil?
|
|
881
1012
|
|
|
882
1013
|
result = values.join(sep)
|
|
@@ -900,14 +1031,13 @@ module Rigor
|
|
|
900
1031
|
end
|
|
901
1032
|
|
|
902
1033
|
# `tuple.inspect` / `tuple.to_s` (`Array#to_s` is an exact alias of `Array#inspect`) — folds when
|
|
903
|
-
# every element is a
|
|
904
|
-
# so the analyzer's Ruby (4.0.x's `Array#inspect` format)
|
|
905
|
-
# Capped at `TUPLE_JOIN_BYTE_LIMIT` like `#join
|
|
906
|
-
# similarly unbounded.
|
|
1034
|
+
# every element is a constant scalar or nested constant Tuple / HashShape, by reconstructing the
|
|
1035
|
+
# Ruby Array and calling the REAL `inspect` so the analyzer's Ruby (4.0.x's `Array#inspect` format)
|
|
1036
|
+
# is matched rather than re-derived. Capped at `TUPLE_JOIN_BYTE_LIMIT` like `#join`.
|
|
907
1037
|
def tuple_inspect(tuple, _method_name, args)
|
|
908
1038
|
return nil unless args.empty?
|
|
909
1039
|
|
|
910
|
-
values =
|
|
1040
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
911
1041
|
return nil if values.nil?
|
|
912
1042
|
|
|
913
1043
|
result = values.inspect
|
|
@@ -952,7 +1082,7 @@ module Rigor
|
|
|
952
1082
|
Type::Combinator.tuple_of(*(tuple.elements * count))
|
|
953
1083
|
end
|
|
954
1084
|
|
|
955
|
-
# `tuple.min` / `tuple.max` — fold when every element is a
|
|
1085
|
+
# `tuple.min` / `tuple.max` — fold when every element is a constant whose values share a
|
|
956
1086
|
# Ruby-comparable domain. Empty tuples fold to `Constant[nil]`. The 1-arg `min(n)` / `max(n)`
|
|
957
1087
|
# form folds to a `Tuple` of the n edge-most values in Ruby's order (`min(n)` ascending, `max(n)`
|
|
958
1088
|
# descending) — the n-arg sibling of `first(n)`.
|
|
@@ -968,11 +1098,11 @@ module Rigor
|
|
|
968
1098
|
return tuple_minmax_n(tuple, args.first, edge) unless args.empty?
|
|
969
1099
|
return Type::Combinator.constant_of(nil) if tuple.elements.empty?
|
|
970
1100
|
|
|
971
|
-
values =
|
|
1101
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
972
1102
|
return nil if values.nil?
|
|
973
1103
|
|
|
974
1104
|
result = values.public_send(edge)
|
|
975
|
-
|
|
1105
|
+
lift_constant_value(result)
|
|
976
1106
|
rescue StandardError
|
|
977
1107
|
nil
|
|
978
1108
|
end
|
|
@@ -985,19 +1115,18 @@ module Rigor
|
|
|
985
1115
|
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer)
|
|
986
1116
|
return nil if arg.value.negative?
|
|
987
1117
|
|
|
988
|
-
values =
|
|
1118
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
989
1119
|
return nil if values.nil?
|
|
990
1120
|
|
|
991
1121
|
picked = values.public_send(edge, arg.value)
|
|
992
|
-
Type::Combinator.tuple_of(*picked.map { |v|
|
|
1122
|
+
Type::Combinator.tuple_of(*picked.map { |v| lift_constant_value(v) })
|
|
993
1123
|
rescue StandardError
|
|
994
1124
|
nil
|
|
995
1125
|
end
|
|
996
1126
|
|
|
997
|
-
# `tuple.minmax` — the `[min, max]` pair as a 2-slot `Tuple
|
|
998
|
-
#
|
|
999
|
-
#
|
|
1000
|
-
# mixed-class values decline.
|
|
1127
|
+
# `tuple.minmax` — the `[min, max]` pair as a 2-slot `Tuple`, mirroring the `Range#minmax` fold.
|
|
1128
|
+
# Every element must be constant and the values must Ruby-compare; an empty tuple folds to
|
|
1129
|
+
# `Tuple[nil, nil]` (Ruby's `[].minmax`), incomparable mixed-class values decline.
|
|
1001
1130
|
def tuple_minmax_pair(tuple, _method_name, args)
|
|
1002
1131
|
return nil unless args.empty?
|
|
1003
1132
|
|
|
@@ -1006,30 +1135,30 @@ module Rigor
|
|
|
1006
1135
|
return Type::Combinator.tuple_of(nil_const, nil_const)
|
|
1007
1136
|
end
|
|
1008
1137
|
|
|
1009
|
-
values =
|
|
1138
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1010
1139
|
return nil if values.nil?
|
|
1011
1140
|
|
|
1012
1141
|
low, high = values.minmax
|
|
1013
1142
|
Type::Combinator.tuple_of(
|
|
1014
|
-
|
|
1015
|
-
|
|
1143
|
+
lift_constant_value(low),
|
|
1144
|
+
lift_constant_value(high)
|
|
1016
1145
|
)
|
|
1017
1146
|
rescue StandardError
|
|
1018
1147
|
nil
|
|
1019
1148
|
end
|
|
1020
1149
|
|
|
1021
|
-
# `tuple.sort` — every element must be
|
|
1150
|
+
# `tuple.sort` — every element must be constant and the values must Ruby-compare. The result
|
|
1022
1151
|
# is a Tuple with the same elements in sorted order. Comparison failures (mixed-class
|
|
1023
1152
|
# incomparable values) decline.
|
|
1024
1153
|
def tuple_sort(tuple, _method_name, args)
|
|
1025
1154
|
return nil unless args.empty?
|
|
1026
1155
|
return tuple if tuple.elements.size <= 1
|
|
1027
1156
|
|
|
1028
|
-
values =
|
|
1157
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1029
1158
|
return nil if values.nil?
|
|
1030
1159
|
|
|
1031
1160
|
sorted = values.sort
|
|
1032
|
-
Type::Combinator.tuple_of(*sorted.map { |v|
|
|
1161
|
+
Type::Combinator.tuple_of(*sorted.map { |v| lift_constant_value(v) })
|
|
1033
1162
|
rescue StandardError
|
|
1034
1163
|
nil
|
|
1035
1164
|
end
|
|
@@ -1056,7 +1185,7 @@ module Rigor
|
|
|
1056
1185
|
# runtime semantics). Declines when any `other_k` is not a Tuple, since the arity is then unknown
|
|
1057
1186
|
# and the result would be `Array[Array[…]]` — RBS already gives that answer.
|
|
1058
1187
|
def tuple_zip(tuple, _method_name, args)
|
|
1059
|
-
return nil if args.
|
|
1188
|
+
return nil if args.size > MAX_ZIP_ARITY
|
|
1060
1189
|
return nil unless args.all?(Type::Tuple)
|
|
1061
1190
|
|
|
1062
1191
|
zipped = tuple.elements.each_with_index.map do |elem, i|
|
|
@@ -1094,23 +1223,180 @@ module Rigor
|
|
|
1094
1223
|
[key.value, value]
|
|
1095
1224
|
end
|
|
1096
1225
|
|
|
1097
|
-
# `tuple.
|
|
1098
|
-
# `
|
|
1099
|
-
|
|
1226
|
+
# `tuple.to_set` — folds to `Constant[Set]` when every element is a `Constant`, matching
|
|
1227
|
+
# `Set[...]` / `Set.new([...])`. Non-constant elements decline so RBS keeps `Set[Elem]`.
|
|
1228
|
+
def tuple_to_set(tuple, _method_name, args)
|
|
1229
|
+
return nil unless args.empty?
|
|
1230
|
+
|
|
1231
|
+
values = constant_values(tuple.elements)
|
|
1232
|
+
return nil if values.nil?
|
|
1233
|
+
|
|
1234
|
+
Type::Combinator.constant_of(::Set.new(values))
|
|
1235
|
+
rescue StandardError
|
|
1236
|
+
nil
|
|
1237
|
+
end
|
|
1238
|
+
|
|
1239
|
+
# `tuple.values_at(i1, i2, ...)` — returns a Tuple of per-index / per-range elements. Each
|
|
1240
|
+
# argument must be a `Constant[Integer]` or a `Constant[Range]` with integer/endless/beginless
|
|
1241
|
+
# endpoints. Out-of-range positive indices fill with `Constant[nil]`, mirroring Ruby's runtime
|
|
1242
|
+
# behaviour. Declines when any argument is non-static, raises `RangeError`, or exceeds
|
|
1243
|
+
# `MAX_SET_OPERATION_SIZE`.
|
|
1100
1244
|
def tuple_values_at(tuple, _method_name, args)
|
|
1101
|
-
return nil if args.empty?
|
|
1245
|
+
return nil if args.empty? || args.size > MAX_SET_OPERATION_SIZE
|
|
1246
|
+
|
|
1247
|
+
raw_args = args.map do |arg|
|
|
1248
|
+
return nil unless arg.is_a?(Type::Constant) && values_at_selector?(arg.value)
|
|
1102
1249
|
|
|
1250
|
+
arg.value
|
|
1251
|
+
end
|
|
1252
|
+
picked = tuple.elements.values_at(*raw_args)
|
|
1253
|
+
return nil if picked.size > MAX_SET_OPERATION_SIZE
|
|
1254
|
+
|
|
1255
|
+
Type::Combinator.tuple_of(*picked.map { |elem| elem || Type::Combinator.constant_of(nil) })
|
|
1256
|
+
rescue StandardError
|
|
1257
|
+
nil
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
def values_at_selector?(value)
|
|
1261
|
+
return true if value.is_a?(Integer)
|
|
1262
|
+
return false unless value.is_a?(Range) && integer_range?(value)
|
|
1263
|
+
|
|
1264
|
+
!(value.end.is_a?(Integer) && value.end > MAX_SET_OPERATION_SIZE)
|
|
1265
|
+
end
|
|
1266
|
+
|
|
1267
|
+
# `tuple.fetch_values(i1, i2, ...)` — returns a Tuple of per-index elements when every argument
|
|
1268
|
+
# is an in-bounds `Constant[Integer]`; an out-of-range or non-static index declines so RBS handles
|
|
1269
|
+
# the possible `IndexError`.
|
|
1270
|
+
def tuple_fetch_values(tuple, _method_name, args)
|
|
1103
1271
|
values = args.map do |arg|
|
|
1104
|
-
return nil unless arg.is_a?(Type::Constant)
|
|
1105
|
-
return nil unless arg.value.is_a?(Integer)
|
|
1272
|
+
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer)
|
|
1106
1273
|
|
|
1107
1274
|
idx = normalise_index(arg.value, tuple.elements.size)
|
|
1108
|
-
|
|
1275
|
+
return nil unless idx
|
|
1276
|
+
|
|
1277
|
+
tuple.elements[idx]
|
|
1109
1278
|
end
|
|
1110
1279
|
|
|
1111
1280
|
Type::Combinator.tuple_of(*values)
|
|
1112
1281
|
end
|
|
1113
1282
|
|
|
1283
|
+
# `tuple.assoc(needle)` / `tuple.rassoc(needle)` — scans inner Tuples in declaration order for a
|
|
1284
|
+
# constant match at slot 0 (`assoc`) or slot 1 (`rassoc`). Known non-array scalars/hashes/ranges
|
|
1285
|
+
# are skipped, while an earlier non-constant slot or opaque element declines.
|
|
1286
|
+
def tuple_assoc(tuple, _method_name, args)
|
|
1287
|
+
tuple_slot_lookup(tuple, args, slot_index: 0)
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
def tuple_rassoc(tuple, _method_name, args)
|
|
1291
|
+
tuple_slot_lookup(tuple, args, slot_index: 1)
|
|
1292
|
+
end
|
|
1293
|
+
|
|
1294
|
+
def tuple_slot_lookup(tuple, args, slot_index:)
|
|
1295
|
+
return nil unless args.size == 1
|
|
1296
|
+
|
|
1297
|
+
needle = unwrap_constant_type(args.first)
|
|
1298
|
+
return nil if needle.equal?(NOT_CONSTANT)
|
|
1299
|
+
|
|
1300
|
+
tuple.elements.each do |elem|
|
|
1301
|
+
next if non_array_shape?(elem)
|
|
1302
|
+
return nil unless elem.is_a?(Type::Tuple)
|
|
1303
|
+
|
|
1304
|
+
match = tuple_slot_match(elem, needle, slot_index)
|
|
1305
|
+
return nil if match == :decline
|
|
1306
|
+
return elem if match
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
Type::Combinator.constant_of(nil)
|
|
1310
|
+
rescue StandardError
|
|
1311
|
+
nil
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
def non_array_shape?(elem)
|
|
1315
|
+
elem.is_a?(Type::Constant) || elem.is_a?(Type::HashShape) ||
|
|
1316
|
+
elem.is_a?(Type::IntegerRange) || elem.is_a?(Type::FloatRange)
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1319
|
+
def tuple_slot_match(elem, needle, slot_index)
|
|
1320
|
+
return false if elem.elements.size <= slot_index
|
|
1321
|
+
|
|
1322
|
+
slot = unwrap_constant_type(elem.elements[slot_index])
|
|
1323
|
+
return :decline if slot.equal?(NOT_CONSTANT)
|
|
1324
|
+
|
|
1325
|
+
slot == needle
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1328
|
+
# `tuple.tally` — returns a closed `HashShape` of `value => Constant[count]` when every element
|
|
1329
|
+
# is a `Constant` whose value is a valid static HashShape key.
|
|
1330
|
+
def tuple_tally(tuple, _method_name, args)
|
|
1331
|
+
return nil unless args.empty?
|
|
1332
|
+
|
|
1333
|
+
values = constant_values(tuple.elements)
|
|
1334
|
+
return nil if values.nil?
|
|
1335
|
+
return nil unless values.all? { |v| static_shape_key?(v) }
|
|
1336
|
+
|
|
1337
|
+
counts = values.tally.transform_values { |n| Type::Combinator.constant_of(n) }
|
|
1338
|
+
Type::Combinator.hash_shape_of(counts)
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
# `tuple.transpose` — transposes a Tuple of equal-length Tuples into a Tuple of Tuples.
|
|
1342
|
+
def tuple_transpose(tuple, _method_name, args)
|
|
1343
|
+
return nil unless args.empty?
|
|
1344
|
+
return tuple if tuple.elements.empty?
|
|
1345
|
+
return nil unless tuple.elements.all?(Type::Tuple)
|
|
1346
|
+
|
|
1347
|
+
width = tuple.elements.first.elements.size
|
|
1348
|
+
return nil unless tuple.elements.all? { |row| row.elements.size == width }
|
|
1349
|
+
|
|
1350
|
+
transposed = tuple.elements.map(&:elements).transpose
|
|
1351
|
+
Type::Combinator.tuple_of(*transposed.map { |col| Type::Combinator.tuple_of(*col) })
|
|
1352
|
+
end
|
|
1353
|
+
|
|
1354
|
+
# `tuple.product(other_1, ...)` — Cartesian product of Tuples, bounded by
|
|
1355
|
+
# `MAX_SET_OPERATION_ARITY` and `MAX_SET_OPERATION_SIZE`.
|
|
1356
|
+
def tuple_product(tuple, _method_name, args)
|
|
1357
|
+
return nil if args.size > MAX_SET_OPERATION_ARITY
|
|
1358
|
+
return nil unless args.all?(Type::Tuple)
|
|
1359
|
+
|
|
1360
|
+
total = args.reduce(tuple.elements.size) { |acc, other| acc * other.elements.size }
|
|
1361
|
+
return nil if total > MAX_SET_OPERATION_SIZE
|
|
1362
|
+
return Type::Combinator.tuple_of if total.zero?
|
|
1363
|
+
|
|
1364
|
+
pairs = tuple.elements.product(*args.map(&:elements))
|
|
1365
|
+
Type::Combinator.tuple_of(*pairs.map { |row| Type::Combinator.tuple_of(*row) })
|
|
1366
|
+
end
|
|
1367
|
+
|
|
1368
|
+
# `tuple.pack(template)` — folds to `Constant[String]` when all elements and template are
|
|
1369
|
+
# `Constant` (excluding non-deterministic pointer directives `p`/`P` and numeric counts or offsets
|
|
1370
|
+
# whose sum exceeds `TUPLE_JOIN_BYTE_LIMIT`), capped at `TUPLE_JOIN_BYTE_LIMIT`.
|
|
1371
|
+
def tuple_pack(tuple, _method_name, args)
|
|
1372
|
+
return nil unless args.size == 1
|
|
1373
|
+
|
|
1374
|
+
template = args.first
|
|
1375
|
+
return nil unless template.is_a?(Type::Constant) && template.value.is_a?(String)
|
|
1376
|
+
return nil if template.value.bytesize > TUPLE_JOIN_BYTE_LIMIT
|
|
1377
|
+
return nil if template.value.match?(/[pP]/)
|
|
1378
|
+
return nil if pack_count_exceeds_limit?(template.value)
|
|
1379
|
+
|
|
1380
|
+
values = constant_values(tuple.elements)
|
|
1381
|
+
return nil if values.nil?
|
|
1382
|
+
|
|
1383
|
+
result = values.pack(template.value)
|
|
1384
|
+
return nil if result.bytesize > TUPLE_JOIN_BYTE_LIMIT
|
|
1385
|
+
|
|
1386
|
+
Type::Combinator.constant_of(result)
|
|
1387
|
+
rescue StandardError
|
|
1388
|
+
nil
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
def pack_count_exceeds_limit?(template)
|
|
1392
|
+
total = 0
|
|
1393
|
+
template.scan(/\d+/) do |digits|
|
|
1394
|
+
total += digits.to_i
|
|
1395
|
+
return true if total > TUPLE_JOIN_BYTE_LIMIT
|
|
1396
|
+
end
|
|
1397
|
+
false
|
|
1398
|
+
end
|
|
1399
|
+
|
|
1114
1400
|
# `tuple + other` — concatenates two Tuples. Both sides must be `Type::Tuple`. Returns a new
|
|
1115
1401
|
# Tuple whose elements are those of the receiver followed by those of the argument.
|
|
1116
1402
|
# `at(n)` — the strict single-Integer accessor. Deliberately NOT an alias of {#tuple_index}: `[]`
|
|
@@ -1133,15 +1419,33 @@ module Rigor
|
|
|
1133
1419
|
# Every set operation is evaluated by running Ruby's own operator over the unwrapped values, which
|
|
1134
1420
|
# is the only way to reproduce its `eql?` / `hash` semantics exactly — `[1] & [1.0]` is empty even
|
|
1135
1421
|
# though `1 == 1.0`, so an equality-based reimplementation would fold the wrong answer.
|
|
1136
|
-
def tuple_intersection(tuple,
|
|
1422
|
+
def tuple_intersection(tuple, method_name, args)
|
|
1423
|
+
return tuple if method_name == :intersection && args.empty?
|
|
1424
|
+
|
|
1137
1425
|
set_operation(tuple, args) { |values, others| others.reduce(values, :&) }
|
|
1138
1426
|
end
|
|
1139
1427
|
|
|
1140
|
-
def tuple_union(tuple,
|
|
1428
|
+
def tuple_union(tuple, method_name, args)
|
|
1429
|
+
return tuple_zero_arg_union(tuple) if method_name == :union && args.empty?
|
|
1430
|
+
|
|
1141
1431
|
set_operation(tuple, args) { |values, others| others.reduce(values, :|) }
|
|
1142
1432
|
end
|
|
1143
1433
|
|
|
1144
|
-
def
|
|
1434
|
+
def tuple_zero_arg_union(tuple)
|
|
1435
|
+
return tuple if tuple.elements.size <= 1
|
|
1436
|
+
|
|
1437
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1438
|
+
return nil if values.nil?
|
|
1439
|
+
|
|
1440
|
+
result = values.union
|
|
1441
|
+
return nil if result.size > MAX_SET_OPERATION_SIZE
|
|
1442
|
+
|
|
1443
|
+
Type::Combinator.tuple_of(*result.map { |value| lift_constant_value(value) })
|
|
1444
|
+
end
|
|
1445
|
+
|
|
1446
|
+
def tuple_difference(tuple, method_name, args)
|
|
1447
|
+
return tuple if method_name == :difference && args.empty?
|
|
1448
|
+
|
|
1145
1449
|
set_operation(tuple, args) { |values, others| others.reduce(values, :-) }
|
|
1146
1450
|
end
|
|
1147
1451
|
|
|
@@ -1158,23 +1462,23 @@ module Rigor
|
|
|
1158
1462
|
|
|
1159
1463
|
# Shared body for the set operations: unwrap the receiver and every Tuple argument, hand the raw
|
|
1160
1464
|
# values to `block`, and lift the result back. Declines unless every element on both sides is a
|
|
1161
|
-
#
|
|
1162
|
-
#
|
|
1465
|
+
# constant scalar or constant nested Tuple / HashShape (an unknown element makes membership
|
|
1466
|
+
# undecidable) and every argument is a `Tuple`.
|
|
1163
1467
|
def set_operation(tuple, args)
|
|
1164
1468
|
return nil if args.empty? || args.size > MAX_SET_OPERATION_ARITY
|
|
1165
1469
|
|
|
1166
|
-
values =
|
|
1470
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1167
1471
|
return nil if values.nil?
|
|
1168
1472
|
|
|
1169
1473
|
others = args.map do |arg|
|
|
1170
1474
|
return nil unless arg.is_a?(Type::Tuple)
|
|
1171
1475
|
|
|
1172
|
-
|
|
1476
|
+
unwrap_constant_elements(arg.elements) || (return nil)
|
|
1173
1477
|
end
|
|
1174
1478
|
result = yield(values, others)
|
|
1175
1479
|
return nil if result.size > MAX_SET_OPERATION_SIZE
|
|
1176
1480
|
|
|
1177
|
-
Type::Combinator.tuple_of(*result.map { |value|
|
|
1481
|
+
Type::Combinator.tuple_of(*result.map { |value| lift_constant_value(value) })
|
|
1178
1482
|
end
|
|
1179
1483
|
|
|
1180
1484
|
# An argument list longer than this, or a result wider than this, declines to the RBS tier rather
|
|
@@ -1204,28 +1508,48 @@ module Rigor
|
|
|
1204
1508
|
Type::Combinator.tuple_of(*tuple.elements, *other.elements)
|
|
1205
1509
|
end
|
|
1206
1510
|
|
|
1207
|
-
# `tuple.compact` — removes every element that is `Constant[nil]`.
|
|
1208
|
-
#
|
|
1209
|
-
#
|
|
1511
|
+
# `tuple.compact` — removes every element that is `Constant[nil]`. When every element is a
|
|
1512
|
+
# `Constant` the nil set is decidable and the result is the exact Tuple of the kept elements.
|
|
1513
|
+
# Otherwise an element that may be nil leaves the arity unknown, so the result is `Array[E]` with
|
|
1514
|
+
# `E` the elements' union minus its `nil` constituent (#2519) — never a fixed-arity Tuple. When
|
|
1515
|
+
# no element carries a `nil` constituent there is nothing to strip and the method declines so
|
|
1516
|
+
# the RBS tier answers (an element such as `Dynamic[top]` may still be nil at runtime, so the
|
|
1517
|
+
# Tuple is not kept either).
|
|
1210
1518
|
def tuple_compact(tuple, _method_name, args)
|
|
1211
1519
|
return nil unless args.empty?
|
|
1212
|
-
return nil unless tuple.elements.all?(Type::Constant)
|
|
1213
1520
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1521
|
+
if tuple.elements.all?(Type::Constant)
|
|
1522
|
+
kept = tuple.elements.reject { |e| e.value.nil? }
|
|
1523
|
+
return Type::Combinator.tuple_of(*kept)
|
|
1524
|
+
end
|
|
1525
|
+
|
|
1526
|
+
tuple_compact_widened(tuple.elements)
|
|
1527
|
+
end
|
|
1528
|
+
|
|
1529
|
+
def tuple_compact_widened(elements)
|
|
1530
|
+
stripped = elements.map { |e| strip_nil_constituent(e) }
|
|
1531
|
+
return nil if stripped.zip(elements).all? { |s, e| s.equal?(e) }
|
|
1532
|
+
|
|
1533
|
+
element = Type::Combinator.union(*stripped)
|
|
1534
|
+
return Type::Combinator.tuple_of if element.is_a?(Type::Bot)
|
|
1535
|
+
|
|
1536
|
+
Type::Combinator.nominal_of("Array", type_args: [element])
|
|
1216
1537
|
end
|
|
1217
1538
|
|
|
1218
1539
|
# `uniq` (no block) → `Tuple` of the first occurrence of each distinct value. Folds only when
|
|
1219
|
-
# every element is a
|
|
1540
|
+
# every element is a constant scalar or constant nested Tuple / HashShape so `eql?` identity is
|
|
1541
|
+
# decidable; the block form defers.
|
|
1220
1542
|
def tuple_uniq(tuple, _method_name, args)
|
|
1221
1543
|
return nil unless args.empty?
|
|
1222
|
-
return nil unless tuple.elements.all?(Type::Constant)
|
|
1223
1544
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1545
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1546
|
+
return nil if values.nil?
|
|
1547
|
+
|
|
1548
|
+
seen = {}
|
|
1549
|
+
kept = tuple.elements.zip(values).each_with_object([]) do |(element, value), acc|
|
|
1550
|
+
next if seen.key?(value)
|
|
1227
1551
|
|
|
1228
|
-
seen
|
|
1552
|
+
seen[value] = true
|
|
1229
1553
|
acc << element
|
|
1230
1554
|
end
|
|
1231
1555
|
Type::Combinator.tuple_of(*kept)
|
|
@@ -1233,9 +1557,9 @@ module Rigor
|
|
|
1233
1557
|
|
|
1234
1558
|
# `index(obj)` / `find_index(obj)` → `Constant[Integer]` of the first element equal to `obj`,
|
|
1235
1559
|
# `Constant[nil]` when none match. Folds only for the argument form (the block form defers) when
|
|
1236
|
-
# every element AND the argument are
|
|
1560
|
+
# every element AND the argument are constant (decidable equality).
|
|
1237
1561
|
def tuple_find_index(tuple, _method_name, args)
|
|
1238
|
-
constant_index(tuple, args) { |
|
|
1562
|
+
constant_index(tuple, args) { |values, needle| values.index { |v| v == needle } }
|
|
1239
1563
|
end
|
|
1240
1564
|
|
|
1241
1565
|
# `tuple.flatten` / `tuple.flatten(depth)` — recursively flattens nested Tuple elements into a
|
|
@@ -1279,21 +1603,24 @@ module Rigor
|
|
|
1279
1603
|
|
|
1280
1604
|
# `rindex(obj)` → the LAST matching index, same decidability gate.
|
|
1281
1605
|
def tuple_rindex(tuple, _method_name, args)
|
|
1282
|
-
constant_index(tuple, args) { |
|
|
1606
|
+
constant_index(tuple, args) { |values, needle| values.rindex { |v| v == needle } }
|
|
1283
1607
|
end
|
|
1284
1608
|
|
|
1285
1609
|
def constant_index(tuple, args)
|
|
1286
1610
|
return nil unless args.size == 1
|
|
1287
1611
|
|
|
1288
|
-
needle = args.first
|
|
1289
|
-
|
|
1290
|
-
return nil
|
|
1612
|
+
needle = unwrap_constant_type(args.first)
|
|
1613
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1614
|
+
return nil if needle.equal?(NOT_CONSTANT) || values.nil?
|
|
1291
1615
|
|
|
1292
|
-
Type::Combinator.constant_of(yield(
|
|
1616
|
+
Type::Combinator.constant_of(yield(values, needle))
|
|
1617
|
+
rescue StandardError
|
|
1618
|
+
nil
|
|
1293
1619
|
end
|
|
1294
1620
|
|
|
1295
1621
|
# `tuple.take(n)` — returns the first n elements as a new Tuple. The argument must be a
|
|
1296
|
-
# `Constant[Integer]`. n
|
|
1622
|
+
# `Constant[Integer]`. `n == 0` returns the empty Tuple, a negative `n` declines, and `n >= size`
|
|
1623
|
+
# returns the full receiver.
|
|
1297
1624
|
def tuple_take(tuple, _method_name, args)
|
|
1298
1625
|
n = non_negative_count_arg(args)
|
|
1299
1626
|
return nil if n.nil?
|
|
@@ -1359,7 +1686,63 @@ module Rigor
|
|
|
1359
1686
|
end
|
|
1360
1687
|
|
|
1361
1688
|
def any_element_matches?(elements, value)
|
|
1362
|
-
elements.any?
|
|
1689
|
+
elements.any? do |e|
|
|
1690
|
+
unwrapped = unwrap_constant_type(e)
|
|
1691
|
+
!unwrapped.equal?(NOT_CONSTANT) && unwrapped == value
|
|
1692
|
+
end
|
|
1693
|
+
end
|
|
1694
|
+
|
|
1695
|
+
NOT_CONSTANT = Object.new.freeze
|
|
1696
|
+
private_constant :NOT_CONSTANT
|
|
1697
|
+
|
|
1698
|
+
# Unwraps a `Type::Constant`, a `Type::Tuple` of recursively constant elements, or a closed
|
|
1699
|
+
# required-only `Type::HashShape` of recursively constant values into its plain Ruby value, or
|
|
1700
|
+
# `NOT_CONSTANT` when any part is non-constant.
|
|
1701
|
+
def unwrap_constant_type(type)
|
|
1702
|
+
case type
|
|
1703
|
+
when Type::Constant
|
|
1704
|
+
type.value
|
|
1705
|
+
when Type::Tuple
|
|
1706
|
+
values = unwrap_constant_elements(type.elements)
|
|
1707
|
+
values.nil? ? NOT_CONSTANT : values
|
|
1708
|
+
when Type::HashShape
|
|
1709
|
+
unwrap_constant_hash_shape(type)
|
|
1710
|
+
else
|
|
1711
|
+
NOT_CONSTANT
|
|
1712
|
+
end
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
def unwrap_constant_hash_shape(shape)
|
|
1716
|
+
return NOT_CONSTANT unless shape.closed? && shape.optional_keys.empty?
|
|
1717
|
+
|
|
1718
|
+
pairs = {}
|
|
1719
|
+
shape.pairs.each do |k, v|
|
|
1720
|
+
unwrapped = unwrap_constant_type(v)
|
|
1721
|
+
return NOT_CONSTANT if unwrapped.equal?(NOT_CONSTANT)
|
|
1722
|
+
|
|
1723
|
+
pairs[k] = unwrapped
|
|
1724
|
+
end
|
|
1725
|
+
pairs
|
|
1726
|
+
end
|
|
1727
|
+
|
|
1728
|
+
def unwrap_constant_elements(elements)
|
|
1729
|
+
elements.map do |elem|
|
|
1730
|
+
val = unwrap_constant_type(elem)
|
|
1731
|
+
return nil if val.equal?(NOT_CONSTANT)
|
|
1732
|
+
|
|
1733
|
+
val
|
|
1734
|
+
end
|
|
1735
|
+
end
|
|
1736
|
+
|
|
1737
|
+
def lift_constant_value(value)
|
|
1738
|
+
case value
|
|
1739
|
+
when Array
|
|
1740
|
+
Type::Combinator.tuple_of(*value.map { |v| lift_constant_value(v) })
|
|
1741
|
+
when Hash
|
|
1742
|
+
Type::Combinator.hash_shape_of(value.transform_values { |v| lift_constant_value(v) })
|
|
1743
|
+
else
|
|
1744
|
+
Type::Combinator.constant_of(value)
|
|
1745
|
+
end
|
|
1363
1746
|
end
|
|
1364
1747
|
|
|
1365
1748
|
# Per-element Constant value extraction. Returns nil when any element is non-Constant, so the
|
|
@@ -1919,7 +2302,7 @@ module Rigor
|
|
|
1919
2302
|
# `shape.values_at(:a, :b, ...)` with a list of static keys. Returns a `Tuple` whose per-position
|
|
1920
2303
|
# values are the per-key value types (`Constant[nil]` for missing keys, mirroring Ruby's runtime
|
|
1921
2304
|
# behaviour). A computed key takes the nil-bearing step {#hash_dig_step} gives it; the call falls
|
|
1922
|
-
# through when any step does (a computed key on an open shape).
|
|
2305
|
+
# through when any step does (a computed key on an open or empty shape).
|
|
1923
2306
|
def hash_values_at(shape, _method_name, args)
|
|
1924
2307
|
return nil if args.empty?
|
|
1925
2308
|
|