rigortype 0.3.8 → 0.3.9
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 +1 -1
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +37 -4
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +14 -1
- data/docs/handbook/11-sig-gen.md +72 -1
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +32 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +11 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +6 -1
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/plugins/rigor-actionpack.md +18 -0
- data/docs/manual/plugins/rigor-activerecord.md +28 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
- data/lib/rigor/analysis/baseline.rb +2 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +200 -32
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +6 -1
- data/lib/rigor/analysis/incremental.rb +16 -0
- data/lib/rigor/analysis/incremental_session.rb +81 -22
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +4 -1
- data/lib/rigor/analysis/run_cache_key.rb +1 -1
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +133 -35
- data/lib/rigor/analysis/worker_session.rb +9 -10
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +155 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +4 -4
- data/lib/rigor/builtins/static_return_refinements.rb +5 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +58 -20
- data/lib/rigor/cache/engine_source.rb +4 -4
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +40 -6
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +79 -11
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +77 -32
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +3 -3
- data/lib/rigor/cli/docs_command.rb +2 -2
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +1 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +2 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +2 -2
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +36 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +10 -6
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +68 -6
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -6
- data/lib/rigor/configuration.rb +43 -4
- data/lib/rigor/effects/attribution.rb +1 -3
- data/lib/rigor/effects/config_envelopes.rb +6 -8
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +0 -1
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +8 -8
- data/lib/rigor/effects/envelope_index.rb +5 -8
- data/lib/rigor/effects/framework_units.rb +4 -6
- data/lib/rigor/effects/identity.rb +4 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/method_key.rb +1 -1
- data/lib/rigor/effects/plugin_facts.rb +4 -6
- data/lib/rigor/effects/propagator.rb +2 -3
- data/lib/rigor/effects/scanner.rb +1 -1
- data/lib/rigor/effects/signature_sources.rb +3 -5
- data/lib/rigor/effects/snapshot.rb +10 -10
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/unit_scan.rb +10 -10
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +51 -4
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +388 -59
- data/lib/rigor/environment.rb +105 -20
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/inference/acceptance.rb +153 -8
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +5 -5
- data/lib/rigor/inference/budget_trace.rb +1 -5
- data/lib/rigor/inference/captured_locals.rb +2 -3
- data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +168 -0
- data/lib/rigor/inference/expression_typer.rb +763 -143
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +1 -5
- data/lib/rigor/inference/macro_block_self_type.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +12 -13
- data/lib/rigor/inference/method_parameter_binder.rb +4 -6
- data/lib/rigor/inference/multi_target_binder.rb +1 -3
- data/lib/rigor/inference/mutation_rejoin.rb +168 -0
- data/lib/rigor/inference/mutation_widening.rb +100 -51
- data/lib/rigor/inference/narrowing.rb +299 -46
- data/lib/rigor/inference/optimistic_origin.rb +1 -7
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +3 -3
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +3 -3
- data/lib/rigor/inference/refinement_mutation.rb +72 -0
- data/lib/rigor/inference/scope_indexer.rb +688 -151
- data/lib/rigor/inference/statement_evaluator.rb +221 -50
- data/lib/rigor/inference/string_mutation.rb +60 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +1 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +2 -2
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +2 -5
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/plugin/base.rb +90 -21
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +7 -7
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +31 -12
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +23 -1
- data/lib/rigor/plugin/registry.rb +42 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +11 -11
- data/lib/rigor/protection/kill_signature.rb +2 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +6 -9
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +61 -21
- data/lib/rigor/reflection/constant_path.rb +126 -0
- data/lib/rigor/reflection.rb +26 -10
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +30 -0
- data/lib/rigor/scope.rb +203 -4
- data/lib/rigor/sig_gen/generator.rb +115 -22
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/observation_collector.rb +17 -15
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +4 -5
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/writer.rb +23 -6
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +2 -5
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +68 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +5 -5
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +3 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/environment.rbs +5 -0
- data/sig/rigor/inference.rbs +3 -10
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/manifest.rbs +2 -1
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +24 -3
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
- data/sig/rigor/type.rbs +27 -1
- data/sig/rigor.rbs +1 -0
- data/skills/rigor-ask/SKILL.md +8 -6
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
- data/skills/rigor-project-init/SKILL.md +6 -3
- data/skills/rigor-project-init/references/02-configure.md +10 -6
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-type-oracle/SKILL.md +228 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
- metadata +26 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -22,10 +22,10 @@ module Rigor
|
|
|
22
22
|
module ForkMap
|
|
23
23
|
module_function
|
|
24
24
|
|
|
25
|
-
# @param items
|
|
26
|
-
# @param workers
|
|
27
|
-
# @yield
|
|
28
|
-
# @return
|
|
25
|
+
# @param items — work items (file paths, or `[path, ast]` pairs), in caller order.
|
|
26
|
+
# @param workers — resolved worker count (≤1, empty items, or no `fork` → sequential).
|
|
27
|
+
# @yield a contiguous slice of `items`; must return a **marshalable** object.
|
|
28
|
+
# @return the per-slice block results, in original slice order.
|
|
29
29
|
def call(items:, workers:, &block)
|
|
30
30
|
worker_count = [workers, items.size].min
|
|
31
31
|
return [block.call(items)] unless parallel?(worker_count) && !items.empty?
|
|
@@ -55,8 +55,6 @@ module Rigor
|
|
|
55
55
|
# The directory check is the belt to {.run_worker}'s braces: the offer is only worth taking while the
|
|
56
56
|
# parent still has the directory open, and answering `nil` once it is gone degrades to `Dir.tmpdir`
|
|
57
57
|
# instead of raising `Errno::ENOENT` somewhere far from here.
|
|
58
|
-
#
|
|
59
|
-
# @return [String, nil]
|
|
60
58
|
def child_scratch_dir
|
|
61
59
|
return nil unless @child_scratch_dir && File.directory?(@child_scratch_dir)
|
|
62
60
|
|
|
@@ -120,7 +118,7 @@ module Rigor
|
|
|
120
118
|
end
|
|
121
119
|
end
|
|
122
120
|
|
|
123
|
-
# @return
|
|
121
|
+
# @return the child's payload wrapped so a legitimately-nil block result is
|
|
124
122
|
# distinguishable from an abnormal exit. `Marshal.load` is safe: the blob was written by our own
|
|
125
123
|
# forked child to a temp file we created.
|
|
126
124
|
def worker_payload(status, out_path)
|
|
@@ -41,10 +41,9 @@ module Rigor
|
|
|
41
41
|
|
|
42
42
|
# Reduce `app` against the registry.
|
|
43
43
|
#
|
|
44
|
-
# @param
|
|
45
|
-
# @param fuel [Integer] reduction-step budget (default 64 per ADR-20 WD3). Each visited
|
|
44
|
+
# @param fuel — reduction-step budget (default 64 per ADR-20 WD3). Each visited
|
|
46
45
|
# body node costs one unit. On exhaustion the reduction returns `app.bound`.
|
|
47
|
-
# @return
|
|
46
|
+
# @return the reduced type, or `app.bound` when reduction is impossible (URI
|
|
48
47
|
# not defined, arity mismatch, body_tree absent, fuel exhausted).
|
|
49
48
|
def reduce(app, fuel: DEFAULT_FUEL)
|
|
50
49
|
raise ArgumentError, "expected a Rigor::Type::App, got #{app.class}" unless app.is_a?(Type::App)
|
|
@@ -99,8 +99,6 @@ module Rigor
|
|
|
99
99
|
|
|
100
100
|
attr_reader :registrations, :definitions
|
|
101
101
|
|
|
102
|
-
# @param registrations [Array<Registration>]
|
|
103
|
-
# @param definitions [Array<Definition>]
|
|
104
102
|
def initialize(registrations: [], definitions: [])
|
|
105
103
|
@registrations = registrations.to_h { |r| [r.uri, r] }.freeze
|
|
106
104
|
@definitions = definitions.to_h { |d| [d.uri, d] }.freeze
|
|
@@ -123,7 +121,7 @@ module Rigor
|
|
|
123
121
|
@definitions[uri]
|
|
124
122
|
end
|
|
125
123
|
|
|
126
|
-
# @return
|
|
124
|
+
# @return a new registry whose entries are the union of this registry's and
|
|
127
125
|
# `other`'s. On URI collisions `other`'s entries win (last-write-wins; OQ3 tentative).
|
|
128
126
|
def merge(other)
|
|
129
127
|
raise ArgumentError, "merge target must be an HktRegistry, got #{other.class}" unless other.is_a?(HktRegistry)
|
|
@@ -154,13 +152,12 @@ module Rigor
|
|
|
154
152
|
# diagnostic; the other annotations still apply). That claim was false until issue #785: the
|
|
155
153
|
# production reporter has no `#record`, so every declined directive was dropped silently.
|
|
156
154
|
#
|
|
157
|
-
# @param
|
|
158
|
-
# @param base [HktRegistry] starting registry (typically the bundled
|
|
155
|
+
# @param base — starting registry (typically the bundled
|
|
159
156
|
# `Rigor::Builtins::HktBuiltins.registry`).
|
|
160
|
-
# @param name_scope
|
|
157
|
+
# @param name_scope — threaded through to the bound
|
|
161
158
|
# resolver for class-name lookups; safe to omit during scanning since hkt bounds are
|
|
162
159
|
# typically `untyped` or stdlib classes.
|
|
163
|
-
# @param reporter
|
|
160
|
+
# @param reporter — same fail-soft reporter contract
|
|
164
161
|
# the other RBS-extended parsers use; a collecting double that responds only to `#record` /
|
|
165
162
|
# `#<<` still works (see {Rigor::RbsExtended::HktDirectives.record_hkt_error}).
|
|
166
163
|
|
|
@@ -27,7 +27,6 @@ module Rigor
|
|
|
27
27
|
|
|
28
28
|
module_function
|
|
29
29
|
|
|
30
|
-
# @param node [Prism::Node]
|
|
31
30
|
def index_write?(node)
|
|
32
31
|
NODE_CLASSES.any? { |klass| node.is_a?(klass) }
|
|
33
32
|
end
|
|
@@ -36,10 +35,7 @@ module Rigor
|
|
|
36
35
|
# the widening joins the stored value into the carrier's content evidence exactly as a real `[]=` does
|
|
37
36
|
# (issue #560). Empty means "no evidence" and widens without joining.
|
|
38
37
|
#
|
|
39
|
-
# @param node
|
|
40
|
-
# @param current_scope [Rigor::Scope]
|
|
41
|
-
# @param arg_types [Array<Rigor::Type::Base>]
|
|
42
|
-
# @return [Rigor::Scope]
|
|
38
|
+
# @param node — one of {NODE_CLASSES}
|
|
43
39
|
def widen(node:, current_scope:, arg_types: MutationWidening::NO_ARG_TYPES)
|
|
44
40
|
MutationWidening.widen_receiver_aliases(node.receiver, MUTATOR, current_scope, arg_types: arg_types)
|
|
45
41
|
end
|
|
@@ -28,10 +28,7 @@ module Rigor
|
|
|
28
28
|
module MacroBlockSelfType
|
|
29
29
|
module_function
|
|
30
30
|
|
|
31
|
-
# @
|
|
32
|
-
# @param call_node [Prism::CallNode]
|
|
33
|
-
# @param receiver_type [Rigor::Type, nil]
|
|
34
|
-
# @return [Rigor::Type, nil] the narrowed self-type, or
|
|
31
|
+
# @return the narrowed self-type, or
|
|
35
32
|
# `nil` when no registered entry matches the call shape.
|
|
36
33
|
def narrow_self_type_for(scope:, call_node:, receiver_type:)
|
|
37
34
|
return nil if receiver_type.nil?
|
|
@@ -46,13 +46,8 @@ module Rigor
|
|
|
46
46
|
# a finitely-sized receiver it is `Constant[size]`.
|
|
47
47
|
COUNT_METHOD = :count
|
|
48
48
|
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# @param args [Array<Rigor::Type>]
|
|
52
|
-
# @param block_type [Rigor::Type, nil] inferred return type of
|
|
53
|
-
# the call's block. `nil` means "no block at the call site"
|
|
54
|
-
# and disqualifies every rule here.
|
|
55
|
-
# @return [Rigor::Type, nil]
|
|
49
|
+
# `context.block_type` is the inferred return type of the call's block; `nil` means "no block at the
|
|
50
|
+
# call site" and disqualifies every rule here.
|
|
56
51
|
def try_dispatch(context)
|
|
57
52
|
receiver = context.receiver
|
|
58
53
|
method_name = context.method_name
|
|
@@ -135,7 +130,6 @@ module Rigor
|
|
|
135
130
|
end
|
|
136
131
|
end
|
|
137
132
|
|
|
138
|
-
# @return [:always_true, :always_false, :bool, nil]
|
|
139
133
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
140
134
|
def predicate_decision(method_name, truthiness, emptiness)
|
|
141
135
|
case method_name
|
|
@@ -167,7 +161,6 @@ module Rigor
|
|
|
167
161
|
)
|
|
168
162
|
end
|
|
169
163
|
|
|
170
|
-
# @return [:empty, :non_empty, :unknown]
|
|
171
164
|
def receiver_emptiness(receiver)
|
|
172
165
|
case receiver
|
|
173
166
|
when Type::Tuple
|
|
@@ -214,7 +214,6 @@ module Rigor
|
|
|
214
214
|
# survives the call.
|
|
215
215
|
CONSTANT_SELF_RETURNERS = %i[freeze itself dup clone].to_set.freeze
|
|
216
216
|
|
|
217
|
-
# @return [Rigor::Type::Constant, Rigor::Type::Union, Rigor::Type::IntegerRange, nil]
|
|
218
217
|
def try_dispatch(context)
|
|
219
218
|
receiver = context.receiver
|
|
220
219
|
method_name = context.method_name
|
|
@@ -234,15 +233,8 @@ module Rigor
|
|
|
234
233
|
first_arg = args.first
|
|
235
234
|
return nil unless first_arg.is_a?(Type::Constant) && first_arg.value.is_a?(Symbol)
|
|
236
235
|
end
|
|
237
|
-
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
# v0.0.7 — `String#%` against a `Tuple` / `HashShape` argument runs Ruby's format-string engine
|
|
241
|
-
# when both sides are statically constant. The standard `numeric_set_of` path bails on Tuple /
|
|
242
|
-
# HashShape arguments because they are not scalar-Constant carriers, so the special-case sits
|
|
243
|
-
# ahead of the numeric path.
|
|
244
|
-
format_lift = try_fold_string_format(receiver, method_name, args)
|
|
245
|
-
return format_lift if format_lift
|
|
236
|
+
ahead = try_fold_ahead_of_numeric_path(receiver, method_name, args)
|
|
237
|
+
return ahead if ahead
|
|
246
238
|
|
|
247
239
|
receiver_set = numeric_set_of(receiver)
|
|
248
240
|
return nil unless receiver_set
|
|
@@ -253,6 +245,20 @@ module Rigor
|
|
|
253
245
|
dispatch_by_arity(receiver_set, method_name, arg_sets)
|
|
254
246
|
end
|
|
255
247
|
|
|
248
|
+
# The folds that must answer before `numeric_set_of` gets a say, because the receiver or an
|
|
249
|
+
# argument is a carrier it declines:
|
|
250
|
+
#
|
|
251
|
+
# - the self-returners, whose receiver may be any `Constant`;
|
|
252
|
+
# - v0.0.7's `String#%` against a `Tuple` / `HashShape` argument, neither a scalar-Constant
|
|
253
|
+
# carrier, which runs Ruby's own format-string engine;
|
|
254
|
+
# - #861's `clamp` on a plain `Integer` / `Float` receiver, a `Nominal` that `numeric_set_of`
|
|
255
|
+
# declines outright.
|
|
256
|
+
def try_fold_ahead_of_numeric_path(receiver, method_name, args)
|
|
257
|
+
try_fold_self_return(receiver, method_name, args) ||
|
|
258
|
+
try_fold_string_format(receiver, method_name, args) ||
|
|
259
|
+
try_fold_unbounded_clamp(receiver, method_name, args)
|
|
260
|
+
end
|
|
261
|
+
|
|
256
262
|
# `freeze` / `itself` / `dup` / `clone` on a value-pinned receiver return the receiver carrier.
|
|
257
263
|
#
|
|
258
264
|
# This sits AHEAD of the catalogue path and deliberately bypasses its purity gate, because the gate
|
|
@@ -344,11 +350,13 @@ module Rigor
|
|
|
344
350
|
return type.members.map(&:value) if type.members.all?(Type::Constant)
|
|
345
351
|
|
|
346
352
|
# A union that mixes `Constant<Integer>` and `IntegerRange` members (e.g. an accumulator's
|
|
347
|
-
# running fixpoint assumption `1 |
|
|
353
|
+
# running fixpoint assumption `1 | Integer[1..6]`) folds as the bounding interval. The
|
|
348
354
|
# range-arithmetic path (`try_fold_binary_range`) then keeps the result an `IntegerRange`
|
|
349
355
|
# instead of bailing to Dynamic.
|
|
350
356
|
union_integer_bounds(type)
|
|
351
|
-
|
|
357
|
+
# ADR-109 — a bounded Float folds through its own arms (`try_fold_unary_float_range`,
|
|
358
|
+
# `try_fold_ternary_float_range`, `try_fold_clamp_range`); every other arm declines it.
|
|
359
|
+
when Type::IntegerRange, Type::FloatRange then type
|
|
352
360
|
end
|
|
353
361
|
end
|
|
354
362
|
|
|
@@ -390,11 +398,15 @@ module Rigor
|
|
|
390
398
|
case set
|
|
391
399
|
when Array then try_fold_unary_set(set, method_name)
|
|
392
400
|
when Type::IntegerRange then try_fold_unary_range(set, method_name)
|
|
401
|
+
when Type::FloatRange then try_fold_unary_float_range(set, method_name)
|
|
393
402
|
end
|
|
394
403
|
end
|
|
395
404
|
|
|
396
405
|
def try_fold_binary(left, method_name, right)
|
|
397
406
|
return try_fold_divmod(left, right) if method_name == :divmod
|
|
407
|
+
return try_fold_clamp_range(left, right) if method_name == :clamp && bounded_range?(left)
|
|
408
|
+
# ADR-109 — no Float-range arithmetic yet; a bounded Float on either side declines here.
|
|
409
|
+
return nil if left.is_a?(Type::FloatRange) || right.is_a?(Type::FloatRange)
|
|
398
410
|
|
|
399
411
|
if left.is_a?(Type::IntegerRange) || right.is_a?(Type::IntegerRange)
|
|
400
412
|
try_fold_binary_range(left, method_name, right)
|
|
@@ -403,6 +415,133 @@ module Rigor
|
|
|
403
415
|
end
|
|
404
416
|
end
|
|
405
417
|
|
|
418
|
+
def bounded_range?(type)
|
|
419
|
+
type.is_a?(Type::IntegerRange) || type.is_a?(Type::FloatRange)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# #834 — `clamp(range)` on a bounded receiver, the one-argument twin of `range_clamp` /
|
|
423
|
+
# `float_range_clamp`. An exclusive end raises at run time (`cannot clamp with an exclusive
|
|
424
|
+
# range`), so it declines rather than folding a call that does not return; a missing end keeps
|
|
425
|
+
# the receiver's own bound on that side. The bracket's endpoints must be literals of the
|
|
426
|
+
# receiver's class.
|
|
427
|
+
def try_fold_clamp_range(receiver, right)
|
|
428
|
+
bracket = clamp_bracket(right)
|
|
429
|
+
return nil if bracket.nil?
|
|
430
|
+
|
|
431
|
+
lower, upper = bracket
|
|
432
|
+
case receiver
|
|
433
|
+
when Type::IntegerRange
|
|
434
|
+
return nil unless clamp_endpoints_are?(Integer, lower, upper)
|
|
435
|
+
|
|
436
|
+
range_clamp(receiver, lower || receiver.lower, upper || receiver.upper)
|
|
437
|
+
when Type::FloatRange
|
|
438
|
+
return nil unless clamp_endpoints_are?(Numeric, lower, upper)
|
|
439
|
+
|
|
440
|
+
float_range_clamp(receiver, lower, upper)
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# `[lower, upper]` of a single literal, closed, non-reversed `Range` argument (either side may be
|
|
445
|
+
# `nil` for an open range); `nil` for any other shape.
|
|
446
|
+
def clamp_bracket(right)
|
|
447
|
+
return nil unless right.is_a?(Array) && right.size == 1 && right.first.is_a?(Range)
|
|
448
|
+
|
|
449
|
+
bracket = right.first
|
|
450
|
+
return nil if bracket.exclude_end? && !bracket.end.nil?
|
|
451
|
+
|
|
452
|
+
lower = bracket.begin
|
|
453
|
+
upper = bracket.end
|
|
454
|
+
return nil if lower && upper && lower > upper
|
|
455
|
+
|
|
456
|
+
[lower, upper]
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def clamp_endpoints_are?(klass, lower, upper)
|
|
460
|
+
(lower.nil? || lower.is_a?(klass)) && (upper.nil? || upper.is_a?(klass))
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# #861 — `clamp` on a receiver that carries no bracket of its own. `Comparable#clamp` returns a
|
|
464
|
+
# value inside the bracket whatever the receiver's bounds are, so a plain `Integer` / `Float`
|
|
465
|
+
# receiver takes the bracket entire: `i.clamp(1..9)` and `i.clamp(1, 9)` are `Integer[1..9]`
|
|
466
|
+
# where the RBS tier's `self | A` could only say `Integer` (resp. `1 | 9 | Integer`).
|
|
467
|
+
#
|
|
468
|
+
# Endpoints must be literals of the receiver's own class. A mixed bracket is `Comparable`'s
|
|
469
|
+
# business rather than the fold's: `1.clamp(0.5, 2.5)` returns the receiver OR a bound, so the
|
|
470
|
+
# run-time class is a union across `Integer` and `Float`, and it declines here.
|
|
471
|
+
#
|
|
472
|
+
# `Float` covers `NaN` while a `FloatRange` never does — sound in this direction, because
|
|
473
|
+
# `clamp` on a NaN receiver raises (`comparison of Float with Float failed`) and the call that
|
|
474
|
+
# returns is always inside the bracket.
|
|
475
|
+
#
|
|
476
|
+
# The two run-time raisers `clamp_bracket` already declines — an exclusive end (`cannot clamp
|
|
477
|
+
# with an exclusive range`) and a reversed bracket — keep declining here.
|
|
478
|
+
def try_fold_unbounded_clamp(receiver, method_name, args)
|
|
479
|
+
return nil unless method_name == :clamp
|
|
480
|
+
|
|
481
|
+
klass = unbounded_clamp_receiver_class(receiver)
|
|
482
|
+
return nil unless klass
|
|
483
|
+
|
|
484
|
+
case args.size
|
|
485
|
+
when 1 then fold_unbounded_clamp_range(klass, args.first)
|
|
486
|
+
when 2 then fold_unbounded_clamp_pair(klass, args)
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# `Integer` / `Float` for a bare nominal receiver of that class; `nil` for everything else,
|
|
491
|
+
# the bounded carriers included — those keep their own intersecting folds.
|
|
492
|
+
def unbounded_clamp_receiver_class(receiver)
|
|
493
|
+
return nil unless receiver.is_a?(Type::Nominal) && receiver.type_args.empty?
|
|
494
|
+
|
|
495
|
+
case receiver.class_name
|
|
496
|
+
when "Integer" then Integer
|
|
497
|
+
when "Float" then Float
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def fold_unbounded_clamp_range(klass, arg)
|
|
502
|
+
bracket = clamp_bracket(numeric_set_of(arg))
|
|
503
|
+
return nil if bracket.nil?
|
|
504
|
+
|
|
505
|
+
lower, upper = bracket
|
|
506
|
+
return nil unless clamp_endpoints_are?(klass, lower, upper)
|
|
507
|
+
return nil if clamp_bound_nan?(lower) || clamp_bound_nan?(upper)
|
|
508
|
+
|
|
509
|
+
unbounded_clamp_result(klass, lower, upper)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def fold_unbounded_clamp_pair(klass, args)
|
|
513
|
+
values = args.map { |arg| single_clamp_bound(klass, numeric_set_of(arg)) }
|
|
514
|
+
lower, upper = values
|
|
515
|
+
return nil if lower.nil? || upper.nil?
|
|
516
|
+
return nil if lower > upper
|
|
517
|
+
|
|
518
|
+
unbounded_clamp_result(klass, lower, upper)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# The two-argument form takes no `nil` bound: `clamp(nil, 9)` is `Comparable`'s own
|
|
522
|
+
# one-sided form, which the catalogue does not route here.
|
|
523
|
+
def single_clamp_bound(klass, values)
|
|
524
|
+
return nil unless values.is_a?(Array) && values.size == 1
|
|
525
|
+
|
|
526
|
+
value = values.first
|
|
527
|
+
return nil unless value.is_a?(klass)
|
|
528
|
+
|
|
529
|
+
clamp_bound_nan?(value) ? nil : value
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# A NaN bound orders against nothing, so `clamp` raises for it — no bracket to fold to.
|
|
533
|
+
def clamp_bound_nan?(bound)
|
|
534
|
+
bound.is_a?(Float) && bound.nan?
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def unbounded_clamp_result(klass, lower, upper)
|
|
538
|
+
if klass == Integer
|
|
539
|
+
build_integer_range(lower || -Float::INFINITY, upper || Float::INFINITY)
|
|
540
|
+
else
|
|
541
|
+
Type::Combinator.float_range(lower || -Float::INFINITY, upper || Float::INFINITY)
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
406
545
|
# `Integer#divmod` and `Float#divmod` return a 2-element array `[quotient, remainder]`. We project
|
|
407
546
|
# that into `Tuple[Constant[q], Constant[r]]` so downstream rules see the precise element types.
|
|
408
547
|
# Union/range receivers are widened per-position: each tuple slot carries the union of quotients
|
|
@@ -870,16 +1009,70 @@ module Rigor
|
|
|
870
1009
|
# could not reach.
|
|
871
1010
|
#
|
|
872
1011
|
# v0.0.6 — IntegerRange-shaped receivers participate in `Comparable#between?` and
|
|
873
|
-
# `Comparable#clamp` folds. `
|
|
874
|
-
# bounds against scalar args; `
|
|
1012
|
+
# `Comparable#clamp` folds. `Integer[a..b].between?(min, max)` decides three-valued via the receiver's
|
|
1013
|
+
# bounds against scalar args; `Integer[a..b].clamp` narrows the receiver's bounds against the bracket.
|
|
875
1014
|
# Other ternary methods over IntegerRange operands still decline.
|
|
876
1015
|
def try_fold_ternary(receiver_set, method_name, arg_sets)
|
|
877
1016
|
return try_fold_ternary_range(receiver_set, method_name, arg_sets) if receiver_set.is_a?(Type::IntegerRange)
|
|
878
|
-
|
|
1017
|
+
if receiver_set.is_a?(Type::FloatRange)
|
|
1018
|
+
return try_fold_ternary_float_range(receiver_set, method_name, arg_sets)
|
|
1019
|
+
end
|
|
1020
|
+
return nil if arg_sets.any? { |s| bounded_range?(s) }
|
|
879
1021
|
|
|
880
1022
|
try_fold_ternary_set(receiver_set, method_name, arg_sets)
|
|
881
1023
|
end
|
|
882
1024
|
|
|
1025
|
+
# ADR-109 — receiver `FloatRange` + two scalar numeric args: `between?` decides three-valued
|
|
1026
|
+
# over the bracket, `clamp` narrows to the bracket (NaN never inhabits the receiver, so the
|
|
1027
|
+
# comparison that would raise on NaN cannot).
|
|
1028
|
+
def try_fold_ternary_float_range(range, method_name, arg_sets)
|
|
1029
|
+
return nil unless arg_sets.all?(Array)
|
|
1030
|
+
|
|
1031
|
+
min_arg = single_real_arg(arg_sets[0])
|
|
1032
|
+
max_arg = single_real_arg(arg_sets[1])
|
|
1033
|
+
return nil if min_arg.nil? || max_arg.nil?
|
|
1034
|
+
return nil if min_arg > max_arg
|
|
1035
|
+
|
|
1036
|
+
case method_name
|
|
1037
|
+
when :between? then float_range_between(range, min_arg, max_arg)
|
|
1038
|
+
when :clamp then float_range_clamp(range, min_arg, max_arg)
|
|
1039
|
+
end
|
|
1040
|
+
end
|
|
1041
|
+
|
|
1042
|
+
def single_real_arg(values)
|
|
1043
|
+
return nil unless values.is_a?(Array) && values.size == 1
|
|
1044
|
+
|
|
1045
|
+
v = values.first
|
|
1046
|
+
return nil unless v.is_a?(Integer) || (v.is_a?(Float) && !v.nan?)
|
|
1047
|
+
|
|
1048
|
+
v
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
def float_range_between(range, min_arg, max_arg)
|
|
1052
|
+
return Type::Combinator.constant_of(false) if range.canonical_max < min_arg || range.min > max_arg
|
|
1053
|
+
return Type::Combinator.constant_of(true) if range.min >= min_arg && range.canonical_max <= max_arg
|
|
1054
|
+
|
|
1055
|
+
bool_union
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
# `Float[a..b].clamp(lo, hi)` (either bound may be `nil` for the one-argument `clamp(lo..)` /
|
|
1059
|
+
# `clamp(..hi)` forms). Keeps the receiver's own end, exclusivity included, where it is the
|
|
1060
|
+
# tighter one. When the bracket lies wholly outside the receiver every value snaps to one bound
|
|
1061
|
+
# and the result is a point Rigor would have to invent, so it declines like the Integer twin.
|
|
1062
|
+
def float_range_clamp(range, min_arg, max_arg)
|
|
1063
|
+
new_min = min_arg.nil? ? range.min : [range.min, min_arg.to_f].max
|
|
1064
|
+
if max_arg.nil? || max_arg.to_f >= range.canonical_max
|
|
1065
|
+
new_max = range.max
|
|
1066
|
+
exclusive = range.exclude_end?
|
|
1067
|
+
else
|
|
1068
|
+
new_max = max_arg.to_f
|
|
1069
|
+
exclusive = false
|
|
1070
|
+
end
|
|
1071
|
+
return nil if new_min > (exclusive ? new_max.prev_float : new_max)
|
|
1072
|
+
|
|
1073
|
+
Type::Combinator.float_range(new_min, new_max, exclude_end: exclusive)
|
|
1074
|
+
end
|
|
1075
|
+
|
|
883
1076
|
# Receiver IntegerRange + two scalar `Constant<Integer>` args — the only IntegerRange-aware ternary
|
|
884
1077
|
# fold today. `between?` returns Trinary truthiness over the bracket; `clamp` returns the
|
|
885
1078
|
# intersected IntegerRange (or a collapsed Constant if the result pins a single point).
|
|
@@ -904,7 +1097,7 @@ module Rigor
|
|
|
904
1097
|
v.is_a?(Integer) ? v : nil
|
|
905
1098
|
end
|
|
906
1099
|
|
|
907
|
-
# `
|
|
1100
|
+
# `Integer[a..b].between?(min, max)`:
|
|
908
1101
|
# - Constant[true] when [a,b] ⊆ [min,max] (and finite).
|
|
909
1102
|
# - Constant[false] when [a,b] ∩ [min,max] is empty.
|
|
910
1103
|
# - bool union otherwise.
|
|
@@ -916,7 +1109,7 @@ module Rigor
|
|
|
916
1109
|
bool_union
|
|
917
1110
|
end
|
|
918
1111
|
|
|
919
|
-
# `
|
|
1112
|
+
# `Integer[a..b].clamp(min, max)`:
|
|
920
1113
|
# - new_lower = max(a, min), new_upper = min(b, max).
|
|
921
1114
|
# - When new_lower > new_upper the bracket excluded the range entirely; the call still returns one
|
|
922
1115
|
# of the bracket bounds at runtime, but Rigor is strictly less precise here than Ruby — decline
|
|
@@ -1112,8 +1305,8 @@ module Rigor
|
|
|
1112
1305
|
end
|
|
1113
1306
|
|
|
1114
1307
|
# Range % Range. Only the `(any range) % (positive constant n)` and `(any range) % (negative
|
|
1115
|
-
# constant n)` cases are folded precisely — the former narrows to `
|
|
1116
|
-
# `
|
|
1308
|
+
# constant n)` cases are folded precisely — the former narrows to `Integer[0..n-1]`, the latter to
|
|
1309
|
+
# `Integer[n+1..0]`. Other shapes fall back to nil.
|
|
1117
1310
|
def range_modulo(_left, right)
|
|
1118
1311
|
return nil unless right.finite? && right.min == right.max
|
|
1119
1312
|
|
|
@@ -1220,6 +1413,99 @@ module Rigor
|
|
|
1220
1413
|
}.freeze
|
|
1221
1414
|
private_constant :UNARY_RANGE_DIRECT
|
|
1222
1415
|
|
|
1416
|
+
# ADR-109 — unary folds over a bounded Float. No bounded range contains NaN, so the predicates
|
|
1417
|
+
# that exist to detect it are decided outright, and the monotone conversions map the two bounds.
|
|
1418
|
+
# An exclusive end is dropped where negation would turn it into an exclusive begin, a shape
|
|
1419
|
+
# Ruby's literal cannot spell: the result is the closed envelope, a superset.
|
|
1420
|
+
FLOAT_RANGE_UNARY = {
|
|
1421
|
+
abs: :float_range_abs,
|
|
1422
|
+
magnitude: :float_range_abs,
|
|
1423
|
+
"-@": :float_range_negate,
|
|
1424
|
+
"+@": :float_range_identity,
|
|
1425
|
+
to_f: :float_range_identity,
|
|
1426
|
+
nan?: :float_range_never_nan,
|
|
1427
|
+
finite?: :float_range_finite,
|
|
1428
|
+
zero?: :float_range_zero,
|
|
1429
|
+
positive?: :float_range_positive,
|
|
1430
|
+
negative?: :float_range_negative,
|
|
1431
|
+
floor: :float_range_to_integer,
|
|
1432
|
+
ceil: :float_range_to_integer,
|
|
1433
|
+
round: :float_range_to_integer,
|
|
1434
|
+
truncate: :float_range_to_integer,
|
|
1435
|
+
to_i: :float_range_to_integer
|
|
1436
|
+
}.freeze
|
|
1437
|
+
private_constant :FLOAT_RANGE_UNARY
|
|
1438
|
+
|
|
1439
|
+
def try_fold_unary_float_range(range, method_name)
|
|
1440
|
+
handler = FLOAT_RANGE_UNARY[method_name]
|
|
1441
|
+
return nil if handler.nil?
|
|
1442
|
+
|
|
1443
|
+
send(handler, range, method_name)
|
|
1444
|
+
end
|
|
1445
|
+
|
|
1446
|
+
def float_range_abs(range, _method_name)
|
|
1447
|
+
lower = range.min
|
|
1448
|
+
upper = range.canonical_max
|
|
1449
|
+
return range if lower >= 0.0
|
|
1450
|
+
return Type::Combinator.float_range(-upper, -lower) if upper <= 0.0
|
|
1451
|
+
|
|
1452
|
+
Type::Combinator.float_range(0.0, [-lower, upper].max)
|
|
1453
|
+
end
|
|
1454
|
+
|
|
1455
|
+
def float_range_negate(range, _method_name)
|
|
1456
|
+
Type::Combinator.float_range(-range.canonical_max, -range.min)
|
|
1457
|
+
end
|
|
1458
|
+
|
|
1459
|
+
def float_range_identity(range, _method_name)
|
|
1460
|
+
range
|
|
1461
|
+
end
|
|
1462
|
+
|
|
1463
|
+
def float_range_never_nan(_range, _method_name)
|
|
1464
|
+
Type::Combinator.constant_of(false)
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1467
|
+
def float_range_finite(range, _method_name)
|
|
1468
|
+
lower = range.min
|
|
1469
|
+
upper = range.canonical_max
|
|
1470
|
+
return Type::Combinator.constant_of(true) if lower.finite? && upper.finite?
|
|
1471
|
+
return Type::Combinator.constant_of(false) if lower == upper
|
|
1472
|
+
|
|
1473
|
+
bool_union
|
|
1474
|
+
end
|
|
1475
|
+
|
|
1476
|
+
def float_range_zero(range, _method_name)
|
|
1477
|
+
return Type::Combinator.constant_of(false) unless range.covers?(0.0)
|
|
1478
|
+
return Type::Combinator.constant_of(true) if range.min.zero? && range.canonical_max.zero?
|
|
1479
|
+
|
|
1480
|
+
bool_union
|
|
1481
|
+
end
|
|
1482
|
+
|
|
1483
|
+
# `Float#positive?` is `self > 0`, `negative?` is `self < 0`.
|
|
1484
|
+
def float_range_positive(range, _method_name)
|
|
1485
|
+
return Type::Combinator.constant_of(true) if range.min > 0.0
|
|
1486
|
+
return Type::Combinator.constant_of(false) if range.canonical_max <= 0.0
|
|
1487
|
+
|
|
1488
|
+
bool_union
|
|
1489
|
+
end
|
|
1490
|
+
|
|
1491
|
+
def float_range_negative(range, _method_name)
|
|
1492
|
+
return Type::Combinator.constant_of(true) if range.canonical_max < 0.0
|
|
1493
|
+
return Type::Combinator.constant_of(false) if range.min >= 0.0
|
|
1494
|
+
|
|
1495
|
+
bool_union
|
|
1496
|
+
end
|
|
1497
|
+
|
|
1498
|
+
# `floor` / `ceil` / `round` / `truncate` / `to_i` are monotone, so the image of the range is the
|
|
1499
|
+
# integer range between the images of its bounds. An infinite bound raises `FloatDomainError` at
|
|
1500
|
+
# run time for the value that reaches it, so the fold declines and the RBS `Integer` stands.
|
|
1501
|
+
def float_range_to_integer(range, method_name)
|
|
1502
|
+
lower = range.min
|
|
1503
|
+
upper = range.canonical_max
|
|
1504
|
+
return nil if lower.infinite? || upper.infinite?
|
|
1505
|
+
|
|
1506
|
+
build_integer_range(lower.public_send(method_name), upper.public_send(method_name))
|
|
1507
|
+
end
|
|
1508
|
+
|
|
1223
1509
|
def try_fold_unary_range(range, method_name)
|
|
1224
1510
|
return range_unary_predicate(range, method_name) if RANGE_UNARY_PREDICATES.include?(method_name)
|
|
1225
1511
|
return range_unary_shift(range, method_name) if RANGE_UNARY_SHIFTS.include?(method_name)
|
|
@@ -31,7 +31,7 @@ module Rigor
|
|
|
31
31
|
# and the reader-redefinition guard are shared with {StructFolding}.
|
|
32
32
|
extend MemberShapeProjection
|
|
33
33
|
|
|
34
|
-
# @return
|
|
34
|
+
# @return the folded result, or nil to defer.
|
|
35
35
|
def try_dispatch(context)
|
|
36
36
|
receiver = context.receiver
|
|
37
37
|
|
|
@@ -16,7 +16,7 @@ module Rigor
|
|
|
16
16
|
# Each rule mirrors Ruby's actual iteration semantics:
|
|
17
17
|
#
|
|
18
18
|
# - `n.times { |i| … }` yields `i ∈ [0, n-1]` when `n > 0`, nothing otherwise. The block-param type is
|
|
19
|
-
# therefore `
|
|
19
|
+
# therefore `Integer[0..n-1]` for a `Constant<Integer>` receiver, `Integer[0..upper-1]` for a finite
|
|
20
20
|
# `IntegerRange`, and `non_negative_int` for any unbounded-above shape.
|
|
21
21
|
# - `a.upto(b) { |i| … }` yields `i ∈ [a, b]` when `a <= b`. Lower bound from the receiver, upper
|
|
22
22
|
# bound from the argument.
|
|
@@ -25,7 +25,7 @@ module Rigor
|
|
|
25
25
|
module IteratorDispatch
|
|
26
26
|
module_function
|
|
27
27
|
|
|
28
|
-
# @return
|
|
28
|
+
# @return block-param types, or
|
|
29
29
|
# nil to fall through to the next tier.
|
|
30
30
|
def block_param_types(context)
|
|
31
31
|
receiver = context.receiver
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
4
|
require_relative "constant_folding"
|
|
5
|
+
require_relative "random_folding"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Inference
|
|
@@ -43,7 +44,7 @@ module Rigor
|
|
|
43
44
|
# every entry is spelling-parity checked. Public (not private_constant) and Ractor-shareable
|
|
44
45
|
# because the gate and the spec both read it.
|
|
45
46
|
INTRINSIC_NAMES = Ractor.make_shareable(
|
|
46
|
-
Set[:Array, :Integer, :Float, :Rational, :Complex, :p, :pp, :String, :Hash, :format, :sprintf]
|
|
47
|
+
Set[:Array, :Integer, :Float, :Rational, :Complex, :p, :pp, :String, :Hash, :format, :sprintf, :rand]
|
|
47
48
|
)
|
|
48
49
|
|
|
49
50
|
# `Kernel#Rational` / `Kernel#Complex` constructor folds. When every argument is a `Type::Constant`
|
|
@@ -101,6 +102,11 @@ module Rigor
|
|
|
101
102
|
return try_string(context) if method_name == :String
|
|
102
103
|
return try_hash(context) if method_name == :Hash
|
|
103
104
|
return try_format(context) if FORMAT_METHODS.include?(method_name)
|
|
105
|
+
# ADR-109 / #833 — `Kernel#rand(a..b)` lands in the literal range, a bounded carrier of the
|
|
106
|
+
# range's own class (never a constant: the generator advances). The other argument shapes
|
|
107
|
+
# stay unfolded on purpose; {RandomFolding} owns the `Random.rand` spelling and the reading.
|
|
108
|
+
# A splat still declines: the reading needs exactly one literal-Range argument.
|
|
109
|
+
return RandomFolding.interval_for_args(args) if method_name == :rand
|
|
104
110
|
|
|
105
111
|
nil
|
|
106
112
|
end
|