rigortype 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- 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 +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- 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 +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -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 +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +261 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +3 -3
- 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/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- 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/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- 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 +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -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 +3 -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 +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- 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 +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- 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 +112 -14
- 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 +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- 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 +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- 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 +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -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 +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -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 +29 -11
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- 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 +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -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/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- 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 +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- 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 +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- 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 +16 -7
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +168 -21
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
- 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/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -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/match_data_folding.rb +159 -0
- 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 +165 -111
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- 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/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- 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 +5 -6
- 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 +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- 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 +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -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/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- 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 +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- 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 +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- 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/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- 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 +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- 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 +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- 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 +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- 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 +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- 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/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- 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 +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -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/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- 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 +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- 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 +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -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/lib/rigor.rb +1 -0
- 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/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- 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 +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -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/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -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 +10 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- 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 +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- 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 +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -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 +2 -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/types.rb +1 -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-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- 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 +2 -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 +12 -7
- 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 +36 -37
- 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 +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -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 +10 -17
- 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/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
- 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 +2 -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 +10 -5
- 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 +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../type"
|
|
4
|
+
require_relative "range_constant"
|
|
5
|
+
require_relative "content_join"
|
|
4
6
|
|
|
5
7
|
module Rigor
|
|
6
8
|
module Inference
|
|
@@ -27,10 +29,7 @@ module Rigor
|
|
|
27
29
|
module Acceptance
|
|
28
30
|
module_function
|
|
29
31
|
|
|
30
|
-
# @param
|
|
31
|
-
# @param other_type [Rigor::Type]
|
|
32
|
-
# @param mode [Symbol] `:gradual` (default) or `:strict`.
|
|
33
|
-
# @return [Rigor::Type::AcceptsResult]
|
|
32
|
+
# @param mode — `:gradual` (default) or `:strict`.
|
|
34
33
|
def accepts(self_type, other_type, mode: :gradual)
|
|
35
34
|
raise ArgumentError, "Acceptance mode #{mode.inspect} is not implemented yet" unless mode == :gradual
|
|
36
35
|
|
|
@@ -63,6 +62,7 @@ module Rigor
|
|
|
63
62
|
Type::Nominal => :accepts_nominal,
|
|
64
63
|
Type::Constant => :accepts_constant,
|
|
65
64
|
Type::IntegerRange => :accepts_integer_range,
|
|
65
|
+
Type::FloatRange => :accepts_float_range,
|
|
66
66
|
Type::Difference => :accepts_difference,
|
|
67
67
|
Type::Refined => :accepts_refined,
|
|
68
68
|
Type::Intersection => :accepts_intersection,
|
|
@@ -75,10 +75,64 @@ module Rigor
|
|
|
75
75
|
}.freeze
|
|
76
76
|
private_constant :TYPE_HANDLERS
|
|
77
77
|
|
|
78
|
+
UNREAD_HASH_REFUSED_KEY = :__rigor_acceptance_unread_hash_refused__
|
|
79
|
+
private_constant :UNREAD_HASH_REFUSED_KEY
|
|
80
|
+
|
|
81
|
+
# Whether a `maybe` for `param` against `arg` rests on a record that could not read a hash's entries: a
|
|
82
|
+
# `Hash` with a gradual arm ({#gradual_hash?}) or an open `HashShape`. The record cannot prove the key set
|
|
83
|
+
# of either, and its `maybe` climbs through whatever holds the record. `OverloadSelector`'s strict pass
|
|
84
|
+
# reads that `maybe` as no evidence for the overload, so this re-asks with each such record answering
|
|
85
|
+
# `no` and reports whether the pair then fails. A `maybe` from anywhere else (a subclass only the Ruby
|
|
86
|
+
# source declares, a splat under a parameter that is no record) survives the re-ask and still counts.
|
|
87
|
+
def maybe_rests_on_unread_hash?(param, arg)
|
|
88
|
+
return false unless contains_type?(param) { |type| type.is_a?(Type::HashShape) }
|
|
89
|
+
return false unless contains_type?(arg) { |type| unread_hash?(type) }
|
|
90
|
+
|
|
91
|
+
refusing_unread_hashes { accepts(param, arg, mode: :gradual).no? }
|
|
92
|
+
end
|
|
93
|
+
|
|
78
94
|
# rubocop:disable-next Metrics/ClassLength
|
|
79
95
|
class << self
|
|
80
96
|
private
|
|
81
97
|
|
|
98
|
+
# The flag lives on the thread, not on a parameter, because the record may sit any number of `accepts`
|
|
99
|
+
# calls below the one asked (a tuple element, a `Hash` value, a union member), and those calls go
|
|
100
|
+
# through each carrier's `Type#accepts`. It is set only for the duration of the block.
|
|
101
|
+
def refusing_unread_hashes
|
|
102
|
+
previous = Thread.current[UNREAD_HASH_REFUSED_KEY]
|
|
103
|
+
Thread.current[UNREAD_HASH_REFUSED_KEY] = true
|
|
104
|
+
yield
|
|
105
|
+
ensure
|
|
106
|
+
Thread.current[UNREAD_HASH_REFUSED_KEY] = previous
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The `maybe` a record answers for a hash whose entries it cannot read, or `no` inside
|
|
110
|
+
# {#refusing_unread_hashes}.
|
|
111
|
+
def unread_hash_maybe(mode, reason)
|
|
112
|
+
return hash_shape_no(mode, "#{reason} (refused as evidence)") if Thread.current[UNREAD_HASH_REFUSED_KEY]
|
|
113
|
+
|
|
114
|
+
Type::AcceptsResult.maybe(mode: mode, reasons: reason)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def unread_hash?(type)
|
|
118
|
+
gradual_hash?(type) || (type.is_a?(Type::HashShape) && type.open?)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def contains_type?(type, &)
|
|
122
|
+
return true if yield(type)
|
|
123
|
+
|
|
124
|
+
components =
|
|
125
|
+
case type
|
|
126
|
+
when Type::Union then type.members
|
|
127
|
+
when Type::Nominal then type.type_args
|
|
128
|
+
when Type::Tuple then type.elements
|
|
129
|
+
when Type::HashShape then type.pairs.values
|
|
130
|
+
when Type::Difference then [type.base]
|
|
131
|
+
else return false
|
|
132
|
+
end
|
|
133
|
+
components.any? { |component| contains_type?(component, &) }
|
|
134
|
+
end
|
|
135
|
+
|
|
82
136
|
def accepts_one(self_type, other_type, mode)
|
|
83
137
|
handler = TYPE_HANDLERS[self_type.class]
|
|
84
138
|
return send(handler, self_type, other_type, mode) if handler
|
|
@@ -201,7 +255,8 @@ module Rigor
|
|
|
201
255
|
# {#accepts_nominal_args});
|
|
202
256
|
# - Constant[v] when v.is_a?(klass(C)). The type_args of self are ignored here because a Constant
|
|
203
257
|
# carries a concrete value, not a generic instantiation, and the analyzer has no way to refute the
|
|
204
|
-
# args from a literal alone.
|
|
258
|
+
# args from a literal alone. A Range value is the one exception — see
|
|
259
|
+
# {#accepts_range_constant_type_arg}.
|
|
205
260
|
# - Tuple[*] when self is the Array (or a supertype) family. The Tuple is projected to
|
|
206
261
|
# `Nominal[Array, [union(elements)]]` so the existing generic-arg machinery handles it.
|
|
207
262
|
# - HashShape{*} when self is the Hash (or a supertype) family, projected to
|
|
@@ -215,6 +270,7 @@ module Rigor
|
|
|
215
270
|
when Type::Singleton then accepts_nominal_from_singleton(self_type, other_type, mode)
|
|
216
271
|
when Type::StructClass, Type::DataClass then accepts_nominal_from_class_factory(self_type, other_type, mode)
|
|
217
272
|
when Type::IntegerRange then accepts_nominal_from_integer_range(self_type, other_type, mode)
|
|
273
|
+
when Type::FloatRange then accepts_nominal_from_float_range(self_type, other_type, mode)
|
|
218
274
|
else accepts_nominal_from_shape(self_type, other_type, mode)
|
|
219
275
|
end
|
|
220
276
|
end
|
|
@@ -311,6 +367,32 @@ module Rigor
|
|
|
311
367
|
end
|
|
312
368
|
end
|
|
313
369
|
|
|
370
|
+
# ADR-109 WD4 — the Float twin of the IntegerRange rule: `Nominal[Float]` and everything Float
|
|
371
|
+
# is-a accept any `FloatRange`; nothing else does.
|
|
372
|
+
FLOAT_NOMINAL_ANCESTORS = %w[Float Numeric Comparable Object BasicObject].freeze
|
|
373
|
+
private_constant :FLOAT_NOMINAL_ANCESTORS
|
|
374
|
+
|
|
375
|
+
def accepts_nominal_from_float_range(self_type, _other_type, mode)
|
|
376
|
+
unless self_type.type_args.empty?
|
|
377
|
+
return Type::AcceptsResult.no(
|
|
378
|
+
mode: mode,
|
|
379
|
+
reasons: "Nominal[#{self_type.class_name}] with type args rejects FloatRange"
|
|
380
|
+
)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
if FLOAT_NOMINAL_ANCESTORS.include?(self_type.class_name)
|
|
384
|
+
Type::AcceptsResult.yes(
|
|
385
|
+
mode: mode,
|
|
386
|
+
reasons: "FloatRange is-a #{self_type.class_name}"
|
|
387
|
+
)
|
|
388
|
+
else
|
|
389
|
+
Type::AcceptsResult.no(
|
|
390
|
+
mode: mode,
|
|
391
|
+
reasons: "Nominal[#{self_type.class_name}] rejects FloatRange"
|
|
392
|
+
)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
314
396
|
# v0.0.2 — meta-type rule. A `Singleton[T]` is the class object for `T`, so it is an instance of
|
|
315
397
|
# `Class` (when `T` is a class) and always an instance of `Module`. Without this rule a method whose
|
|
316
398
|
# parameter is typed `Class | Module` would reject every `is_a?(SomeClass)` call and similar
|
|
@@ -492,7 +574,7 @@ module Rigor
|
|
|
492
574
|
|
|
493
575
|
def accepts_nominal_from_constant(self_type, constant, mode)
|
|
494
576
|
ruby_class = resolve_class(self_type.class_name)
|
|
495
|
-
return
|
|
577
|
+
return accepts_loaded_class_from_constant(ruby_class, constant, self_type, mode) if ruby_class
|
|
496
578
|
|
|
497
579
|
# The host process may not have required the constant's declared self_type (e.g. `BigDecimal`
|
|
498
580
|
# since Ruby 3.4 is no longer a default gem). Fall back to inspecting the value's own class
|
|
@@ -527,6 +609,13 @@ module Rigor
|
|
|
527
609
|
end
|
|
528
610
|
end
|
|
529
611
|
|
|
612
|
+
def accepts_loaded_class_from_constant(ruby_class, constant, self_type, mode)
|
|
613
|
+
result = constant_is_a_result(ruby_class, constant, self_type, mode)
|
|
614
|
+
return result unless result.yes?
|
|
615
|
+
|
|
616
|
+
accepts_range_constant_type_arg(self_type, constant, mode) || result
|
|
617
|
+
end
|
|
618
|
+
|
|
530
619
|
def constant_is_a_result(ruby_class, constant, self_type, mode)
|
|
531
620
|
if constant.value.is_a?(ruby_class)
|
|
532
621
|
Type::AcceptsResult.yes(mode: mode, reasons: "Constant value is_a?(#{self_type.class_name})")
|
|
@@ -538,10 +627,53 @@ module Rigor
|
|
|
538
627
|
end
|
|
539
628
|
end
|
|
540
629
|
|
|
630
|
+
# Issue #833 — the single exception to "the type_args of self are ignored for a Constant".
|
|
631
|
+
#
|
|
632
|
+
# A `Constant<Range>` is the one literal that does pin a generic instantiation: the engine
|
|
633
|
+
# builds it only when both endpoints are static, so `Range[T]` CAN be refuted from the literal.
|
|
634
|
+
# Without this, `Nominal[Range, [Integer]]` accepted `0.0...1.0` and `OverloadSelector` pass 1
|
|
635
|
+
# pinned whichever `Range[…]` arm came first in declaration order — for `Kernel#rand` that is
|
|
636
|
+
# `(::Range[Integer]) -> Integer?`, ahead of every Float arm.
|
|
637
|
+
#
|
|
638
|
+
# Returns nil — "no opinion, keep the class-level answer" — outside the envelope: a raw `Range`
|
|
639
|
+
# or any other arity of type_args, and a range the shared reader cannot read endpoints from.
|
|
640
|
+
# A missing endpoint contributes nothing, because a beginless or endless literal inhabits
|
|
641
|
+
# `Range[T]` for every T (`Range[Integer?]` is the common core spelling of the slicing param).
|
|
642
|
+
#
|
|
643
|
+
# Known coarse spot, deliberately not special-cased: a mixed-endpoint literal (`1.0..2`) is
|
|
644
|
+
# rejected by `Range[Integer]` and by `Range[Float]` alike, so overload selection falls through
|
|
645
|
+
# to its gradual pass and then to the first-overload fallback exactly as it did before.
|
|
646
|
+
def accepts_range_constant_type_arg(self_type, constant, mode)
|
|
647
|
+
return nil unless self_type.type_args.size == 1
|
|
648
|
+
|
|
649
|
+
endpoints = RangeConstant.literal_endpoints(constant)
|
|
650
|
+
return nil if endpoints.nil?
|
|
651
|
+
|
|
652
|
+
element = self_type.type_args.first
|
|
653
|
+
combine_range_endpoint_results(
|
|
654
|
+
endpoints.map { |endpoint| accepts(element, Type::Combinator.constant_of(endpoint), mode: mode) },
|
|
655
|
+
element,
|
|
656
|
+
mode
|
|
657
|
+
)
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def combine_range_endpoint_results(per_endpoint, element, mode)
|
|
661
|
+
if per_endpoint.any?(&:no?)
|
|
662
|
+
Type::AcceptsResult.no(mode: mode, reasons: "a Range endpoint is rejected by #{element.describe}")
|
|
663
|
+
elsif per_endpoint.any?(&:maybe?)
|
|
664
|
+
Type::AcceptsResult.maybe(
|
|
665
|
+
mode: mode,
|
|
666
|
+
reasons: "a Range endpoint could not be proven accepted by #{element.describe}"
|
|
667
|
+
)
|
|
668
|
+
else
|
|
669
|
+
Type::AcceptsResult.yes(mode: mode, reasons: "every Range endpoint accepted by #{element.describe}")
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
|
|
541
673
|
# IntegerRange[a..b] accepts:
|
|
542
674
|
# - Constant[n] where n is an Integer covered by [a..b];
|
|
543
675
|
# - IntegerRange[c..d] where [c..d] ⊆ [a..b];
|
|
544
|
-
# - Nominal[Integer] only when self is the universal range (`
|
|
676
|
+
# - Nominal[Integer] only when self is the universal range (`Integer[min..max]`), since otherwise an
|
|
545
677
|
# arbitrary Integer could fall outside the bound.
|
|
546
678
|
# Anything else is rejected.
|
|
547
679
|
def accepts_integer_range(self_type, other_type, mode)
|
|
@@ -616,13 +748,77 @@ module Rigor
|
|
|
616
748
|
end
|
|
617
749
|
end
|
|
618
750
|
|
|
751
|
+
# ADR-109 WD4 — `FloatRange` accepts:
|
|
752
|
+
# - Constant[f] where f is a Float the range covers (never NaN: `cover?` compares);
|
|
753
|
+
# - FloatRange[c..d] whose canonical closed bounds sit inside self's.
|
|
754
|
+
# `Nominal[Float]` is never accepted, not even by the universal range: an arbitrary Float may be
|
|
755
|
+
# NaN, and the universal range is exactly "every Float except NaN".
|
|
756
|
+
def accepts_float_range(self_type, other_type, mode)
|
|
757
|
+
case other_type
|
|
758
|
+
when Type::Constant
|
|
759
|
+
accepts_float_range_from_constant(self_type, other_type, mode)
|
|
760
|
+
when Type::FloatRange
|
|
761
|
+
accepts_float_range_from_float_range(self_type, other_type, mode)
|
|
762
|
+
when Type::Nominal
|
|
763
|
+
Type::AcceptsResult.no(
|
|
764
|
+
mode: mode,
|
|
765
|
+
reasons: "#{self_type.describe} rejects Nominal[#{other_type.class_name}] (could be NaN or fall outside)"
|
|
766
|
+
)
|
|
767
|
+
else
|
|
768
|
+
Type::AcceptsResult.no(
|
|
769
|
+
mode: mode,
|
|
770
|
+
reasons: "FloatRange rejects #{other_type.class}"
|
|
771
|
+
)
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def accepts_float_range_from_constant(self_type, constant, mode)
|
|
776
|
+
unless constant.value.is_a?(Float)
|
|
777
|
+
return Type::AcceptsResult.no(
|
|
778
|
+
mode: mode,
|
|
779
|
+
reasons: "FloatRange rejects non-Float Constant"
|
|
780
|
+
)
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
if self_type.covers?(constant.value)
|
|
784
|
+
Type::AcceptsResult.yes(
|
|
785
|
+
mode: mode,
|
|
786
|
+
reasons: "Constant[#{constant.value}] is in #{self_type.describe}"
|
|
787
|
+
)
|
|
788
|
+
else
|
|
789
|
+
Type::AcceptsResult.no(
|
|
790
|
+
mode: mode,
|
|
791
|
+
reasons: "Constant[#{constant.value}] outside #{self_type.describe}"
|
|
792
|
+
)
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
def accepts_float_range_from_float_range(self_type, other_range, mode)
|
|
797
|
+
if self_type.min <= other_range.min && other_range.canonical_max <= self_type.canonical_max
|
|
798
|
+
Type::AcceptsResult.yes(
|
|
799
|
+
mode: mode,
|
|
800
|
+
reasons: "#{other_range.describe} ⊆ #{self_type.describe}"
|
|
801
|
+
)
|
|
802
|
+
else
|
|
803
|
+
Type::AcceptsResult.no(
|
|
804
|
+
mode: mode,
|
|
805
|
+
reasons: "#{other_range.describe} not contained in #{self_type.describe}"
|
|
806
|
+
)
|
|
807
|
+
end
|
|
808
|
+
end
|
|
809
|
+
|
|
619
810
|
# `Difference[base, removed]` accepts another type X when the base accepts X *and* X's value set is
|
|
620
811
|
# provably disjoint from `removed`. The disjointness test is the subtle part — it is NOT the same as
|
|
621
812
|
# `removed.accepts(X)`, because `Nominal[String]` includes `""` even though `Constant[""]` does not
|
|
622
|
-
# "accept" `Nominal[String]`. The conservative rule here: we can prove disjointness only when X is
|
|
623
|
-
#
|
|
624
|
-
# removed value (already exhibits the disjointness)
|
|
625
|
-
#
|
|
813
|
+
# "accept" `Nominal[String]`. The conservative rule here: we can prove disjointness only when X is a
|
|
814
|
+
# carrier whose value set is visibly apart from `removed` — a `Constant` (compare values directly),
|
|
815
|
+
# another `Difference` with the same removed value (already exhibits the disjointness), an
|
|
816
|
+
# `IntegerRange` that skips an Integer removed value, a `Tuple` whose fixed arity differs from a
|
|
817
|
+
# removed `Tuple`, or a `HashShape` with a required key a closed removed `HashShape` cannot hold. The
|
|
818
|
+
# last three cover the empty witnesses of `non-zero-int`, `non-empty-array[T]` and
|
|
819
|
+
# `non-empty-hash[K, V]`. Any other carrier — a Nominal above all — is no proof and the difference
|
|
820
|
+
# rejects it under gradual mode, even where its values happen to exclude `removed`. A Union or
|
|
821
|
+
# Intersection argument never reaches this rule whole: `accepts` splits it into members first.
|
|
626
822
|
def accepts_difference(self_type, other_type, mode)
|
|
627
823
|
base_result = accepts(self_type.base, other_type, mode: mode)
|
|
628
824
|
return base_result if base_result.no?
|
|
@@ -651,9 +847,27 @@ module Rigor
|
|
|
651
847
|
# Disjointness is monotonic over Intersection: if any member is provably disjoint from
|
|
652
848
|
# `removed`, the meet is too.
|
|
653
849
|
other_type.members.any? { |m| provably_disjoint_from_removed?(m, removed) }
|
|
850
|
+
when Type::IntegerRange
|
|
851
|
+
removed.is_a?(Type::Constant) && removed.value.is_a?(Integer) && !other_type.covers?(removed.value)
|
|
852
|
+
when Type::Tuple
|
|
853
|
+
# A Tuple's arity is fixed, so it shares no inhabitant with a Tuple of another arity — the
|
|
854
|
+
# zero-arity witness `non-empty-array[T]` removes included.
|
|
855
|
+
removed.is_a?(Type::Tuple) && removed.elements.size != other_type.elements.size
|
|
856
|
+
when Type::HashShape
|
|
857
|
+
hash_shape_excludes_removed?(other_type, removed)
|
|
654
858
|
end
|
|
655
859
|
end
|
|
656
860
|
|
|
861
|
+
# Every inhabitant of `shape` holds each of its required keys, and a closed `removed` shape holds no
|
|
862
|
+
# key outside its pairs, so one required key `removed` does not list makes the two disjoint. Against
|
|
863
|
+
# the closed `{}` witness `non-empty-hash[K, V]` removes, that is any required key; an optional key
|
|
864
|
+
# proves nothing, since the shape is inhabited without it.
|
|
865
|
+
def hash_shape_excludes_removed?(shape, removed)
|
|
866
|
+
return false unless removed.is_a?(Type::HashShape) && removed.closed?
|
|
867
|
+
|
|
868
|
+
shape.required_keys.any? { |key| !removed.pairs.key?(key) }
|
|
869
|
+
end
|
|
870
|
+
|
|
657
871
|
# `Refined[base, predicate]` accepts another type X when the base accepts the *base* of X *and* X is
|
|
658
872
|
# provably contained in the predicate's value set. The base check is delegated to
|
|
659
873
|
# `accepts(self.base, X.base)` so handlers like `accepts_nominal` see Nominal-vs-Nominal and return
|
|
@@ -788,11 +1002,15 @@ module Rigor
|
|
|
788
1002
|
|
|
789
1003
|
# HashShape{k1: T1, ...} accepts another HashShape when every required key of self is required on
|
|
790
1004
|
# the other side and Ti accepts Ui (depth covariant). Optional keys may be absent on the other side;
|
|
791
|
-
# when present, their values are checked. A closed self rejects known
|
|
792
|
-
#
|
|
1005
|
+
# when present, their values are checked. A closed self rejects known extra keys, and answers at most
|
|
1006
|
+
# `maybe` for an open source: `h = { a: 1 }; h.default = 0` reopens a shape whose key set is still
|
|
1007
|
+
# exactly the literal's. Other types are rejected, except a `Hash` with a gradual arm (see
|
|
1008
|
+
# {#gradual_hash?}); the converse direction (a Nominal accepting a HashShape) is handled by
|
|
793
1009
|
# `accepts_nominal` via projection.
|
|
794
1010
|
def accepts_hash_shape(self_type, other_type, mode)
|
|
795
1011
|
unless other_type.is_a?(Type::HashShape)
|
|
1012
|
+
return gradual_hash_verdict(self_type, other_type, mode) if gradual_hash?(other_type)
|
|
1013
|
+
|
|
796
1014
|
return Type::AcceptsResult.no(
|
|
797
1015
|
mode: mode,
|
|
798
1016
|
reasons: "HashShape does not accept #{other_type.class}"
|
|
@@ -803,8 +1021,6 @@ module Rigor
|
|
|
803
1021
|
return hash_shape_no(mode, "HashShape missing required keys: #{missing.inspect}") unless missing.empty?
|
|
804
1022
|
|
|
805
1023
|
if self_type.closed?
|
|
806
|
-
return hash_shape_no(mode, "HashShape closed target rejects open source") if other_type.open?
|
|
807
|
-
|
|
808
1024
|
extra = other_type.pairs.keys - self_type.pairs.keys
|
|
809
1025
|
unless extra.empty?
|
|
810
1026
|
return hash_shape_no(mode, "HashShape closed target rejects extra keys: #{extra.inspect}")
|
|
@@ -812,9 +1028,59 @@ module Rigor
|
|
|
812
1028
|
end
|
|
813
1029
|
|
|
814
1030
|
per_entry = hash_shape_entry_results(self_type, other_type, mode)
|
|
815
|
-
combine_arg_results(per_entry, mode)
|
|
1031
|
+
result = combine_arg_results(per_entry, mode)
|
|
1032
|
+
return result unless result.yes? && self_type.closed? && other_type.open?
|
|
1033
|
+
|
|
1034
|
+
unread_hash_maybe(mode, "HashShape closed target cannot check open source")
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
# A `Hash` nominal — directly or as the base of a `Difference` (`non-empty-hash[K, V]`) — that is raw or
|
|
1038
|
+
# carries a `Dynamic` arm in a type argument. A hash literal with a `**splat` entry carries the arm by
|
|
1039
|
+
# design, `Hash.new` filled key by key reads as the raw `Hash`, and a declared `Hash[Symbol, untyped]`
|
|
1040
|
+
# is the author saying the hash may hold anything (the reading `MutationRejoin#regrowable_carrier?`
|
|
1041
|
+
# gives a declared gradual arm). The runtime value may then hold exactly the record's keys, so a shape
|
|
1042
|
+
# cannot reject it on the arm's account, the way `Hash[K, V]` accepts a raw `Hash` as maybe. A `Hash`
|
|
1043
|
+
# subclass is not read: its entries need not be the record's (`HashWithIndifferentAccess` stores String
|
|
1044
|
+
# keys).
|
|
1045
|
+
def gradual_hash?(type)
|
|
1046
|
+
nominal = type.is_a?(Type::Difference) ? type.base : type
|
|
1047
|
+
nominal.is_a?(Type::Nominal) && nominal.class_name == "Hash" &&
|
|
1048
|
+
(nominal.type_args.empty? || nominal.type_args.any? { |arg| gradual_arm?(arg) })
|
|
816
1049
|
end
|
|
817
1050
|
|
|
1051
|
+
# `maybe`, unless an argument without a gradual arm already rules the record out: a precise `K` that
|
|
1052
|
+
# rejects a required key (`Hash[String, untyped]` never holds `:a`), or a precise `V` none of whose
|
|
1053
|
+
# members a required key's type accepts.
|
|
1054
|
+
def gradual_hash_verdict(self_type, other_type, mode)
|
|
1055
|
+
nominal = other_type.is_a?(Type::Difference) ? other_type.base : other_type
|
|
1056
|
+
key_type, value_type = nominal.type_args
|
|
1057
|
+
missing = unholdable_keys(self_type, key_type, mode)
|
|
1058
|
+
return hash_shape_no(mode, "Hash key type cannot hold required keys: #{missing.inspect}") if missing.any?
|
|
1059
|
+
|
|
1060
|
+
refused = refused_value_keys(self_type, value_type, mode)
|
|
1061
|
+
return hash_shape_no(mode, "Hash value type refused for required keys: #{refused.inspect}") if refused.any?
|
|
1062
|
+
|
|
1063
|
+
unread_hash_maybe(mode, "HashShape cannot check #{other_type.describe(:short)}")
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
def unholdable_keys(self_type, key_type, mode)
|
|
1067
|
+
return [] if key_type.nil? || gradual_arm?(key_type)
|
|
1068
|
+
|
|
1069
|
+
self_type.required_keys.select { |key| key_type.accepts(Type::Combinator.constant_of(key), mode: mode).no? }
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
def refused_value_keys(self_type, value_type, mode)
|
|
1073
|
+
return [] if value_type.nil? || gradual_arm?(value_type)
|
|
1074
|
+
|
|
1075
|
+
members = ContentJoin.union_members(value_type)
|
|
1076
|
+
self_type.required_keys.select do |key|
|
|
1077
|
+
formal = self_type.pairs.fetch(key)
|
|
1078
|
+
members.all? { |member| accepts(formal, member, mode: mode).no? }
|
|
1079
|
+
end
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
def gradual_arm?(type) = ContentJoin.union_members(type).any?(Type::Dynamic)
|
|
1083
|
+
|
|
818
1084
|
def hash_shape_entry_results(self_type, other_type, mode)
|
|
819
1085
|
self_type.pairs.filter_map do |key, formal|
|
|
820
1086
|
next unless other_type.pairs.key?(key)
|
|
@@ -936,7 +1202,7 @@ module Rigor
|
|
|
936
1202
|
def resolve_class(name)
|
|
937
1203
|
return nil unless name.is_a?(::String)
|
|
938
1204
|
|
|
939
|
-
parts = name
|
|
1205
|
+
parts = class_name_parts(name)
|
|
940
1206
|
return nil if parts.empty?
|
|
941
1207
|
|
|
942
1208
|
mod = ::Object
|
|
@@ -950,7 +1216,16 @@ module Rigor
|
|
|
950
1216
|
rescue ::StandardError, ::ScriptError, ::SystemExit
|
|
951
1217
|
nil
|
|
952
1218
|
end
|
|
1219
|
+
|
|
1220
|
+
# #775 — the split of a class name is a pure function of the string, and the subtype check asks it
|
|
1221
|
+
# twice per Nominal-vs-Nominal comparison (~370k times on the lib self-check) for the same few
|
|
1222
|
+
# hundred names. Only the split is memoised; the constant walk above still runs every time, so a
|
|
1223
|
+
# class loaded (or a spec constant removed) after the first ask is answered by the live tree.
|
|
1224
|
+
def class_name_parts(name)
|
|
1225
|
+
@class_name_parts[name] ||= name.delete_prefix("::").split("::").freeze
|
|
1226
|
+
end
|
|
953
1227
|
end
|
|
1228
|
+
@class_name_parts = {}
|
|
954
1229
|
end
|
|
955
1230
|
end
|
|
956
1231
|
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
require_relative "multi_target_binder"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
# The auto-splat half of {BlockParameterBinder}: given a block's positional parameter shape and the one
|
|
9
|
+
# value the receiver yields, it answers the positional table the block's names are bound from.
|
|
10
|
+
#
|
|
11
|
+
# Ruby blocks (NOT lambdas) spread a single yielded array-ish value across their positionals, and CRuby
|
|
12
|
+
# decides that on the PARAMETER LIST alone ({ParameterShape#splats?}) — never on the value. So the
|
|
13
|
+
# carrier only decides how precisely each position can be filled, and a carrier this module cannot
|
|
14
|
+
# decompose lowers the positions to `Dynamic[Top]` rather than cancelling the spread (issue #1116).
|
|
15
|
+
#
|
|
16
|
+
# Each member of the carrier (a union distributes; anything else is its own single member) contributes
|
|
17
|
+
# one {Table}, and {join} folds them into the answer:
|
|
18
|
+
#
|
|
19
|
+
# - a `Type::Tuple` spreads element-wise, leading positionals (required, then optional) from the head
|
|
20
|
+
# and, with a rest present, trailing positionals from the tail with the rest absorbing the middle —
|
|
21
|
+
# the split `MultiTargetBinder` applies to `a, *r, b = tuple`, so `|*r, v|` over `[K, V]` binds `v` to
|
|
22
|
+
# `V`. A slot past the tuple is left unfilled and lands on `Dynamic[Top]`;
|
|
23
|
+
# - an `Array[T]` ({MultiTargetBinder.array_element_type}) binds `T` to every required and trailing
|
|
24
|
+
# positional, `Dynamic[Top]` to every optional one (a short array hands it its default, not `nil`),
|
|
25
|
+
# and `Array[T]` to a named rest. A short array pads the fixed positions with `nil` at runtime, so
|
|
26
|
+
# those positions are reported optimistic (issue #1093);
|
|
27
|
+
# - an opaque array carrier — a raw `Array`, an `Array[untyped]` / `Array[top]`, a `Refined` /
|
|
28
|
+
# `Difference` over either, or a `Dynamic` over any `Array` however precise — binds `Dynamic[Top]` to
|
|
29
|
+
# every position: the floor `MultiTargetBinder` binds for the same undecomposable right-hand side, and
|
|
30
|
+
# the reason `[1, 2].tap { |a, b| a.succ }` no longer reads `a` as the whole `Array` (issue #1116);
|
|
31
|
+
# - a bare `nil` binds `nil` to every position. `nil` has no `to_ary`, so CRuby passes it as the one
|
|
32
|
+
# argument and `|a, b|` sees `nil, nil` — which is also what the wrap `[nil]` pads to, so the two
|
|
33
|
+
# readings agree here, and {join} takes that `nil` back out of any position another member fills;
|
|
34
|
+
# - every other member takes the same `Dynamic[Top]` floor as the opaque one, and does NOT license the
|
|
35
|
+
# spread on its own. It is the member the union knows least about, and joining it as a value would put
|
|
36
|
+
# a nil-bearing element beside a nil-free one: `String#scan`'s `String | Array[String?]` yield joins a
|
|
37
|
+
# capture's `String?` against the whole match's `String` and fires `call.possible-nil-receiver` on the
|
|
38
|
+
# correlated-invariant `refinements[name.to_sym]`, which ADR-5 ranks above the precision.
|
|
39
|
+
#
|
|
40
|
+
# The spread needs at least one Tuple, `Array[T]` or opaque array member; a carrier with none of them
|
|
41
|
+
# ({#for} answers nil) leaves the binder's declared types alone, which binds the value to the first
|
|
42
|
+
# parameter and `Dynamic[Top]` after. So a lone value with no `to_ary` is NOT wrapped as `[value]` the
|
|
43
|
+
# way `a, b = value` wraps it: the one-value premise of auto-splat is the RBS block signature's arity,
|
|
44
|
+
# which nothing here can check against the actual yield, and a wrong wrap binds `nil` to a parameter the
|
|
45
|
+
# block receives a value in.
|
|
46
|
+
#
|
|
47
|
+
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
48
|
+
module BlockAutoSplat
|
|
49
|
+
# The positional part of a parameter list, as counts: all the auto-splat rule reads. An explicit
|
|
50
|
+
# `BlockParametersNode` list and a numbered-parameter block (`maximum` required positionals) both
|
|
51
|
+
# reduce to it, so the two spellings cannot drift.
|
|
52
|
+
ParameterShape = Data.define(:required, :optional, :post, :rest) do
|
|
53
|
+
def self.of(params_node)
|
|
54
|
+
new(required: params_node.requireds.size, optional: params_node.optionals.size,
|
|
55
|
+
post: params_node.posts.size, rest: !params_node.rest.nil?)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The `_1` / `_2` form, read as the explicit list of `arity` required positionals.
|
|
59
|
+
def self.of_arity(arity)
|
|
60
|
+
new(required: arity, optional: 0, post: 0, rest: false)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def positional_count = required + optional + post
|
|
64
|
+
|
|
65
|
+
# CRuby's own condition (`vm_callee_setup_block_arg` / `setup_parameters_complex`): a block splats a
|
|
66
|
+
# lone array argument when it has a mandatory positional (`lead + post > 0`) or more than one
|
|
67
|
+
# optional, except a bare `|a|` (the iseq's `ambiguous_param0`). So `|k, *r|`, `|*r, v|`,
|
|
68
|
+
# `|a = 1, b = 2|` and the trailing-comma `|k,|` (Prism's `ImplicitRestNode`) splat, while `|*r|`,
|
|
69
|
+
# `|a = 1, *r|` and `|a, &b|` / `|a, k: 1|` do not.
|
|
70
|
+
def splats?
|
|
71
|
+
mandatory = required + post
|
|
72
|
+
return false unless mandatory.positive? || optional > 1
|
|
73
|
+
|
|
74
|
+
!(mandatory == 1 && optional.zero? && !rest)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# A positional table: the type per position, the positions bound optimistically, and the named rest's
|
|
79
|
+
# type (nil for the `Array[Dynamic[Top]]` default). One arm produces one; {join} folds them into one.
|
|
80
|
+
# Private as a name — {#for} hands the folded value out, and nothing outside constructs one.
|
|
81
|
+
Table = Data.define(:types, :optimistic_positions, :rest_type)
|
|
82
|
+
private_constant :Table
|
|
83
|
+
|
|
84
|
+
# The arm kinds that make a member an array as far as the spread is concerned, so that a union
|
|
85
|
+
# carrying one of them spreads even where its other members do not decompose.
|
|
86
|
+
ARRAY_CARRIER_ARMS = %i[tuple array opaque].freeze
|
|
87
|
+
private_constant :ARRAY_CARRIER_ARMS
|
|
88
|
+
|
|
89
|
+
module_function
|
|
90
|
+
|
|
91
|
+
# @param shape — the block's {ParameterShape}.
|
|
92
|
+
# @param carrier — the one value the receiver yields.
|
|
93
|
+
# @return the folded {Table}, or nil when no member is an array carrier and the
|
|
94
|
+
# spread must leave the declared types alone.
|
|
95
|
+
def for(shape, carrier)
|
|
96
|
+
arms = arms_of(carrier)
|
|
97
|
+
return nil if arms.nil?
|
|
98
|
+
|
|
99
|
+
join(arms.map { |member, kind| table_for(shape, member, kind) })
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def arms_of(carrier)
|
|
103
|
+
members = carrier.is_a?(Type::Union) ? carrier.members : [carrier]
|
|
104
|
+
arms = members.map { |member| [member, arm_of(member)] }
|
|
105
|
+
arms.any? { |_, kind| ARRAY_CARRIER_ARMS.include?(kind) } ? arms : nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def arm_of(member)
|
|
109
|
+
return :tuple if member.is_a?(Type::Tuple)
|
|
110
|
+
return :array if MultiTargetBinder.array_element_type(member)
|
|
111
|
+
return :opaque if opaque_array_carrier?(member)
|
|
112
|
+
return :nil_value if nil_literal?(member)
|
|
113
|
+
|
|
114
|
+
:unknown
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# A carrier that is an array the module may not decompose into per-position types: the raw `Array`, an
|
|
118
|
+
# `Array[untyped]` / `Array[top]`, a `Refined` / `Difference` over either, and a `Dynamic` over ANY
|
|
119
|
+
# carrier that would license the spread — `Dynamic[Array[Integer]]`, `Dynamic[[Integer, String]]`,
|
|
120
|
+
# `Dynamic[Array[Integer] | nil]` alike. {MultiTargetBinder.array_element_type} declines every
|
|
121
|
+
# `Dynamic` wrapper on purpose: the value is gradual, and projecting its static facet would hand each
|
|
122
|
+
# position a bare `T` that licenses the negative rules (issue #1093, ADR-5). That is a reason to floor
|
|
123
|
+
# the positions, not a reason to leave the whole value on the first parameter, so the facet decides
|
|
124
|
+
# only whether CRuby would splat.
|
|
125
|
+
def opaque_array_carrier?(type)
|
|
126
|
+
case type
|
|
127
|
+
when Type::Nominal then type.class_name == "Array"
|
|
128
|
+
when Type::Refined, Type::Difference then opaque_array_carrier?(type.base)
|
|
129
|
+
when Type::Dynamic then !arms_of(type.static_facet).nil?
|
|
130
|
+
else false
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def table_for(shape, member, kind)
|
|
135
|
+
case kind
|
|
136
|
+
when :tuple then tuple_table(shape, member.elements)
|
|
137
|
+
when :array then array_table(shape, MultiTargetBinder.array_element_type(member))
|
|
138
|
+
when :nil_value then uniform_table(shape, member)
|
|
139
|
+
else uniform_table(shape, Type::Combinator.untyped)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# The split is mirrored rather than delegated to `MultiTargetBinder.decompose_tuple` because that one
|
|
144
|
+
# pads a missing slot with `Constant[nil]` and softens `X | nil`; a block slot past the tuple has
|
|
145
|
+
# always bound `Dynamic[Top]`. Without a rest the trailing positionals continue from the head (Ruby
|
|
146
|
+
# fills `|a, b = 1, c|` from the head when the tuple is long enough).
|
|
147
|
+
def tuple_table(shape, elements)
|
|
148
|
+
head = shape.required + shape.optional
|
|
149
|
+
posts = shape.post
|
|
150
|
+
tail_start = shape.rest ? [elements.size - posts, head].max : head
|
|
151
|
+
types = Array.new(head) { |i| elements[i] } + Array.new(posts) { |j| elements[tail_start + j] }
|
|
152
|
+
Table.new(types: types, optimistic_positions: [], rest_type: nil)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def array_table(shape, element)
|
|
156
|
+
required = shape.required
|
|
157
|
+
optional = shape.optional
|
|
158
|
+
posts = shape.post
|
|
159
|
+
Table.new(
|
|
160
|
+
types: Array.new(required) { element } + Array.new(optional) { Type::Combinator.untyped } +
|
|
161
|
+
Array.new(posts) { element },
|
|
162
|
+
optimistic_positions: (0...required).to_a + ((required + optional)...(required + optional + posts)).to_a,
|
|
163
|
+
rest_type: Type::Combinator.nominal_of("Array", type_args: [element])
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# One type for every position: the `Dynamic[Top]` floor of a member the module cannot decompose, or
|
|
168
|
+
# the `nil` a `nil` member hands each of them. The `nil` rest_type leaves a named rest on the binder's
|
|
169
|
+
# `Array[Dynamic[Top]]` default, which is right for both — CRuby gives `nil.tap { |a, *r| }` an empty
|
|
170
|
+
# `r`, and an opaque carrier says nothing about the leftovers.
|
|
171
|
+
def uniform_table(shape, type)
|
|
172
|
+
Table.new(types: Array.new(shape.positional_count) { type }, optimistic_positions: [], rest_type: nil)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# A single member's table is used as is. Across members, a position any member leaves unfilled (a slot
|
|
176
|
+
# past a short Tuple) or fills with `Dynamic[Top]` stays `Dynamic[Top]`, and a member's bare `nil`
|
|
177
|
+
# drops out of a position another member fills with a value, that position then being optimistic —
|
|
178
|
+
# `MultiTargetBinder`'s cross-member softening. A named rest joins only when every member supplies
|
|
179
|
+
# one; otherwise the binder's `Array[Dynamic[Top]]` default stands.
|
|
180
|
+
def join(tables)
|
|
181
|
+
types, softened = join_types(tables.map(&:types))
|
|
182
|
+
rests = tables.map(&:rest_type)
|
|
183
|
+
Table.new(
|
|
184
|
+
types: types,
|
|
185
|
+
optimistic_positions: (tables.flat_map(&:optimistic_positions) + softened).uniq,
|
|
186
|
+
rest_type: rests.include?(nil) ? nil : Type::Combinator.union(*rests)
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# @return the joined table and the positions it softened.
|
|
191
|
+
def join_types(tables)
|
|
192
|
+
return [tables.first, []] if tables.size == 1
|
|
193
|
+
|
|
194
|
+
softened = []
|
|
195
|
+
joined = Array.new(tables.map(&:size).max) do |i|
|
|
196
|
+
column = tables.map { |table| table[i] }
|
|
197
|
+
next Type::Combinator.untyped if column.any? { |t| t.nil? || t == Type::Combinator.untyped }
|
|
198
|
+
|
|
199
|
+
firm = column.reject { |t| nil_literal?(t) }
|
|
200
|
+
next Type::Combinator.union(*column) if firm.empty? || firm.size == column.size
|
|
201
|
+
|
|
202
|
+
softened << i
|
|
203
|
+
Type::Combinator.union(*firm)
|
|
204
|
+
end
|
|
205
|
+
[joined, softened]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def nil_literal?(type) = type.is_a?(Type::Constant) && type.value.nil?
|
|
209
|
+
|
|
210
|
+
# {#for} is the whole surface; the arms and the fold are this module's own business, and keeping them
|
|
211
|
+
# off the singleton stops a caller reaching past the fold for one member's table.
|
|
212
|
+
private_class_method :arms_of, :arm_of, :opaque_array_carrier?, :table_for, :tuple_table, :array_table,
|
|
213
|
+
:uniform_table, :join, :join_types, :nil_literal?
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|