rigortype 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
|
@@ -6,63 +6,46 @@ require_relative "call_context"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Inference
|
|
8
8
|
module MethodDispatcher
|
|
9
|
-
# Slice 5 phase 2 shape-aware dispatch tier. Sits between
|
|
10
|
-
# {
|
|
11
|
-
# and {RbsDispatch} (which projects shape carriers to their
|
|
9
|
+
# Slice 5 phase 2 shape-aware dispatch tier. Sits between {ConstantFolding} (which folds
|
|
10
|
+
# Constant-on-Constant arithmetic) and {RbsDispatch} (which projects shape carriers to their
|
|
12
11
|
# underlying nominal and resolves return types through RBS).
|
|
13
12
|
#
|
|
14
|
-
# The tier resolves a curated catalogue of element-access
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# out-of-range index, multi-arg `dig`, ...) the tier returns
|
|
20
|
-
# `nil` so the surrounding pipeline falls through to
|
|
21
|
-
# {RbsDispatch} and the projection-based answer.
|
|
13
|
+
# The tier resolves a curated catalogue of element-access methods on `Rigor::Type::Tuple` and
|
|
14
|
+
# `Rigor::Type::HashShape` receivers, returning the *precise* member type rather than the projected
|
|
15
|
+
# `Array#[]` / `Hash#fetch` result. When the dispatch cannot prove which element will be returned
|
|
16
|
+
# (non-static key, out-of-range index, multi-arg `dig`, ...) the tier returns `nil` so the
|
|
17
|
+
# surrounding pipeline falls through to {RbsDispatch} and the projection-based answer.
|
|
22
18
|
#
|
|
23
19
|
# Catalogue (Slice 5 phase 2):
|
|
24
20
|
#
|
|
25
|
-
# - Tuple#`first`, Tuple#`last`, Tuple#`size`/`length`/`count`:
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# `Constant[nil]`
|
|
32
|
-
#
|
|
33
|
-
# `Constant[Symbol|String]` arguments (Slice 5 phase 2 sub-
|
|
34
|
-
# phase 2). Each step recurses through the resolved member; a
|
|
35
|
-
# missing key/index along the chain collapses to `Constant[nil]`
|
|
36
|
-
# so the carrier surfaces through downstream narrowing. A
|
|
37
|
-
# non-shape intermediate falls through to the projection
|
|
38
|
-
# answer.
|
|
21
|
+
# - Tuple#`first`, Tuple#`last`, Tuple#`size`/`length`/`count`: no-arg, no-block.
|
|
22
|
+
# - Tuple#`[]`, Tuple#`fetch` with a single `Constant[Integer]` argument inside the tuple's bounds
|
|
23
|
+
# (negative indices are normalised by length). Tuple#`[]` also handles static Range and
|
|
24
|
+
# start-length slices, returning a sliced Tuple or `Constant[nil]` for statically nil slices.
|
|
25
|
+
# - Tuple#`dig` with a chain of `Constant[Integer]` / `Constant[Symbol|String]` arguments (Slice 5
|
|
26
|
+
# phase 2 sub-phase 2). Each step recurses through the resolved member; a missing key/index along
|
|
27
|
+
# the chain collapses to `Constant[nil]` so the carrier surfaces through downstream narrowing. A
|
|
28
|
+
# non-shape intermediate falls through to the projection answer.
|
|
39
29
|
# - HashShape#`size`/`length`: no-arg.
|
|
40
|
-
# - HashShape#`[]`, HashShape#`fetch`, HashShape#`dig` with a
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
# `
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# - HashShape#`
|
|
47
|
-
# phase 2).
|
|
48
|
-
#
|
|
49
|
-
# arguments (Slice 5 phase 2 sub-phase 2). The result is a
|
|
50
|
-
# `Tuple` whose elements are the per-key values
|
|
51
|
-
# (`Constant[nil]` for missing keys, mirroring Ruby's runtime
|
|
52
|
-
# behaviour).
|
|
30
|
+
# - HashShape#`[]`, HashShape#`fetch`, HashShape#`dig` with a single `Constant[Symbol|String]`
|
|
31
|
+
# argument matching one of the declared keys. `[]` and `dig` resolve missing keys to
|
|
32
|
+
# `Constant[nil]`; `fetch` (no default, no block) falls through on a miss because Ruby would raise
|
|
33
|
+
# `KeyError` and the analyzer prefers the conservative projection answer.
|
|
34
|
+
# - HashShape#`dig` with multi-arg chains (Slice 5 phase 2 sub-phase 2). Same chaining semantics as
|
|
35
|
+
# Tuple#`dig`.
|
|
36
|
+
# - HashShape#`values_at` with a list of `Constant[Symbol|String]` arguments (Slice 5 phase 2
|
|
37
|
+
# sub-phase 2). The result is a `Tuple` whose elements are the per-key values (`Constant[nil]` for
|
|
38
|
+
# missing keys, mirroring Ruby's runtime behaviour).
|
|
53
39
|
#
|
|
54
40
|
# Methods that this tier does NOT yet handle (they fall through):
|
|
55
41
|
#
|
|
56
|
-
# - Iteration methods that bind block parameters (`each`, `map`,
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# effect model so read-only entries and mutation invalidation
|
|
61
|
-
# have one place to report diagnostics.
|
|
42
|
+
# - Iteration methods that bind block parameters (`each`, `map`, `select`, ...). Those land alongside
|
|
43
|
+
# the BlockNode-aware scope builder.
|
|
44
|
+
# - Tuple/HashShape mutation methods. These land with the future effect model so read-only entries
|
|
45
|
+
# and mutation invalidation have one place to report diagnostics.
|
|
62
46
|
#
|
|
63
|
-
# See docs/internal-spec/inference-engine.md (Slice 5 phase 2)
|
|
64
|
-
#
|
|
65
|
-
# rationale.
|
|
47
|
+
# See docs/internal-spec/inference-engine.md (Slice 5 phase 2) and docs/adr/4-type-inference-engine.md
|
|
48
|
+
# for the slice rationale.
|
|
66
49
|
# rubocop:disable Metrics/ClassLength, Metrics/ModuleLength
|
|
67
50
|
module ShapeDispatch
|
|
68
51
|
module_function
|
|
@@ -109,8 +92,8 @@ module Rigor
|
|
|
109
92
|
itself: :shape_self
|
|
110
93
|
}.freeze
|
|
111
94
|
|
|
112
|
-
# Byte cap on a folded `tuple.join` result — a huge tuple times a
|
|
113
|
-
#
|
|
95
|
+
# Byte cap on a folded `tuple.join` result — a huge tuple times a long separator must not
|
|
96
|
+
# materialise an unbounded `Constant`.
|
|
114
97
|
TUPLE_JOIN_BYTE_LIMIT = 4096
|
|
115
98
|
private_constant :TUPLE_JOIN_BYTE_LIMIT
|
|
116
99
|
|
|
@@ -156,29 +139,25 @@ module Rigor
|
|
|
156
139
|
:<= => :hash_compare,
|
|
157
140
|
:> => :hash_compare,
|
|
158
141
|
:>= => :hash_compare,
|
|
159
|
-
# ADR-76 WD2 / ADR-78 WD3 — pure self-returners preserve the
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
# `
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
# block-form over-fold guard landed in `try_dispatch` — ADR-78
|
|
169
|
-
# WD1 — so `CONST = [...].freeze; CONST.any? { … }` no longer
|
|
170
|
-
# folds the no-block result and fires a reflexive always-truthy.)
|
|
142
|
+
# ADR-76 WD2 / ADR-78 WD3 — pure self-returners preserve the `HashShape` carrier instead of
|
|
143
|
+
# degrading to the nominal `Hash` via the RBS `() -> self` signature, so
|
|
144
|
+
# `MESSAGES = {…}.freeze; MESSAGES[reason]` folds the value union rather than reading `Dynamic`.
|
|
145
|
+
# `dup` / `clone` produce a fresh object, but Rigor's shape carriers are immutable values, so
|
|
146
|
+
# preserving the carrier is sound for reads; a later in-place mutation routes through
|
|
147
|
+
# `MutationWidening`. (The `Tuple` table carries the same four entries; both became safe once the
|
|
148
|
+
# block-form over-fold guard landed in `try_dispatch` — ADR-78 WD1 — so
|
|
149
|
+
# `CONST = [...].freeze; CONST.any? { … }` no longer folds the no-block result and fires a
|
|
150
|
+
# reflexive always-truthy.)
|
|
171
151
|
freeze: :shape_self,
|
|
172
152
|
dup: :shape_self,
|
|
173
153
|
clone: :shape_self,
|
|
174
154
|
itself: :shape_self
|
|
175
155
|
}.freeze
|
|
176
156
|
|
|
177
|
-
# @return [Rigor::Type, nil] the precise element/value type, or
|
|
178
|
-
#
|
|
179
|
-
# Per-carrier dispatch table. Adding a new carrier here
|
|
180
|
-
#
|
|
181
|
-
# Anonymous Type subclasses are not expected.
|
|
157
|
+
# @return [Rigor::Type, nil] the precise element/value type, or `nil` to defer to the next
|
|
158
|
+
# dispatcher tier.
|
|
159
|
+
# Per-carrier dispatch table. Adding a new carrier here is a one-row change; the helper methods
|
|
160
|
+
# stay private. Anonymous Type subclasses are not expected.
|
|
182
161
|
RECEIVER_HANDLERS = {
|
|
183
162
|
Type::Tuple => :dispatch_tuple,
|
|
184
163
|
Type::HashShape => :dispatch_hash_shape,
|
|
@@ -190,13 +169,10 @@ module Rigor
|
|
|
190
169
|
}.freeze
|
|
191
170
|
private_constant :RECEIVER_HANDLERS
|
|
192
171
|
|
|
193
|
-
# v0.1.1 Track 1 slice 5b — `Integer#to_s(base)` on a
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
# result lifts to the matching imported refinement.
|
|
198
|
-
# Bases not listed (2, 36, ...) keep the v0.1.0 baseline
|
|
199
|
-
# since Rigor has no carrier for the resulting alphabet.
|
|
172
|
+
# v0.1.1 Track 1 slice 5b — `Integer#to_s(base)` on a non-negative `IntegerRange` receiver. The
|
|
173
|
+
# output of `n.to_s(b)` for `n >= 0` is digit-string-only (no leading sign), so when the base is in
|
|
174
|
+
# this table the result lifts to the matching imported refinement. Bases not listed (2, 36, ...)
|
|
175
|
+
# keep the v0.1.0 baseline since Rigor has no carrier for the resulting alphabet.
|
|
200
176
|
TO_S_BASE_REFINEMENTS = {
|
|
201
177
|
10 => :decimal_int_string,
|
|
202
178
|
8 => :octal_int_string,
|
|
@@ -209,15 +185,13 @@ module Rigor
|
|
|
209
185
|
method_name = context.method_name
|
|
210
186
|
args = context.args
|
|
211
187
|
args ||= []
|
|
212
|
-
# ADR-78 WD1 — every shape handler folds *no-block* semantics; none
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
# preservation
|
|
219
|
-
# `CONST = [...].freeze; CONST.any? { … }`; fixing it at the fold (not
|
|
220
|
-
# the rule) unblocks that preservation.
|
|
188
|
+
# ADR-78 WD1 — every shape handler folds *no-block* semantics; none evaluates a passed block. So
|
|
189
|
+
# a block-form call (`tuple.any? { … }`, `tuple.sum { … }`, `tuple.count { … }`) must NOT fold
|
|
190
|
+
# the no-block result — doing so ignores the block (an over-fold: `any? { false }` would still
|
|
191
|
+
# fold `Constant[true]`). Declining defers to BlockFolding / RBS. This is the over-fold class the
|
|
192
|
+
# Tuple shape-carrier preservation (ADR-76 WD2) surfaced as reflexive `always-truthy` on
|
|
193
|
+
# `CONST = [...].freeze; CONST.any? { … }`; fixing it at the fold (not the rule) unblocks that
|
|
194
|
+
# preservation.
|
|
221
195
|
return nil unless context.block_type.nil?
|
|
222
196
|
|
|
223
197
|
handler = RECEIVER_HANDLERS[receiver.class]
|
|
@@ -226,11 +200,9 @@ module Rigor
|
|
|
226
200
|
send(handler, receiver, method_name, args)
|
|
227
201
|
end
|
|
228
202
|
|
|
229
|
-
# Tightens `Array#size` / `Array#length` / `String#length` /
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
# tier ahead of RBS sees the more precise carrier so
|
|
233
|
-
# downstream narrowing (`if size > 0; …`) actually has a
|
|
203
|
+
# Tightens `Array#size` / `Array#length` / `String#length` / `String#bytesize` / `Hash#size` etc.
|
|
204
|
+
# on a `Nominal` receiver from the RBS-declared `Integer` to `non_negative_int`. The tier ahead of
|
|
205
|
+
# RBS sees the more precise carrier so downstream narrowing (`if size > 0; …`) actually has a
|
|
234
206
|
# range to intersect with.
|
|
235
207
|
SIZE_RETURNING_NOMINALS = Ractor.make_shareable({
|
|
236
208
|
"Array" => %i[size length count],
|
|
@@ -241,12 +213,10 @@ module Rigor
|
|
|
241
213
|
})
|
|
242
214
|
private_constant :SIZE_RETURNING_NOMINALS
|
|
243
215
|
|
|
244
|
-
# When the difference removes the empty value of the
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
# `
|
|
248
|
-
# non-negative range minus the removed point's `0`),
|
|
249
|
-
# and `empty?` / `zero?` MUST be `Constant[false]`.
|
|
216
|
+
# When the difference removes the empty value of the base type (`Constant[""]`, `Constant[0]`, an
|
|
217
|
+
# empty Tuple, an empty HashShape), `size` / `length` / `count` MUST be `positive-int` (the base's
|
|
218
|
+
# non-negative range minus the removed point's `0`), and `empty?` / `zero?` MUST be
|
|
219
|
+
# `Constant[false]`.
|
|
250
220
|
EMPTY_REMOVAL_BASES = %w[String Array Hash Set].freeze
|
|
251
221
|
private_constant :EMPTY_REMOVAL_BASES
|
|
252
222
|
|
|
@@ -267,10 +237,9 @@ module Rigor
|
|
|
267
237
|
send(handler, shape, method_name, args)
|
|
268
238
|
end
|
|
269
239
|
|
|
270
|
-
# ADR-76 WD2 / ADR-78 WD3 — a pure self-returner
|
|
271
|
-
#
|
|
272
|
-
#
|
|
273
|
-
# `() -> self` RBS signature would otherwise drop.
|
|
240
|
+
# ADR-76 WD2 / ADR-78 WD3 — a pure self-returner (`freeze` / `dup` / `clone` / `itself`) returns
|
|
241
|
+
# the receiver carrier unchanged, preserving the shape that the nominal `() -> self` RBS
|
|
242
|
+
# signature would otherwise drop.
|
|
274
243
|
def shape_self(carrier, _method_name, _args)
|
|
275
244
|
carrier
|
|
276
245
|
end
|
|
@@ -287,10 +256,9 @@ module Rigor
|
|
|
287
256
|
Type::Combinator.non_negative_int
|
|
288
257
|
end
|
|
289
258
|
|
|
290
|
-
# Arg-/method-driven precision projections for a `Nominal`
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
# skip the work. Returns nil when no projection applies.
|
|
259
|
+
# Arg-/method-driven precision projections for a `Nominal` receiver, consulted ahead of the
|
|
260
|
+
# no-arg size tier. Each branch gates on the class name first so unrelated nominals skip the
|
|
261
|
+
# work. Returns nil when no projection applies.
|
|
294
262
|
def nominal_projection(nominal, method_name, args)
|
|
295
263
|
case nominal.class_name
|
|
296
264
|
when "String"
|
|
@@ -305,14 +273,12 @@ module Rigor
|
|
|
305
273
|
end
|
|
306
274
|
end
|
|
307
275
|
|
|
308
|
-
# `Array[T]#compact` — `compact` removes every `nil` element,
|
|
309
|
-
#
|
|
310
|
-
#
|
|
311
|
-
# the `
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
# `T` has no `nil` constituent to remove (the result equals the
|
|
315
|
-
# receiver, so the RBS tier's answer is already precise).
|
|
276
|
+
# `Array[T]#compact` — `compact` removes every `nil` element, so the result element type is `T`
|
|
277
|
+
# with its `nil` constituent stripped (`Array[Node?]#compact` → `Array[Node]`). Mirrors the
|
|
278
|
+
# `Tuple#compact` constant fold for the generic element case. Declines when the receiver carries
|
|
279
|
+
# no type argument (the RBS `Array[untyped]` answer is already maximal) or when `T` has no `nil`
|
|
280
|
+
# constituent to remove (the result equals the receiver, so the RBS tier's answer is already
|
|
281
|
+
# precise).
|
|
316
282
|
def array_nominal_compact(nominal, args)
|
|
317
283
|
return nil unless args.empty?
|
|
318
284
|
|
|
@@ -325,10 +291,9 @@ module Rigor
|
|
|
325
291
|
Type::Combinator.nominal_of("Array", type_args: [stripped])
|
|
326
292
|
end
|
|
327
293
|
|
|
328
|
-
# Removes the `nil` constituent from a (possibly union) type,
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
# dispatch tier to avoid a dependency on the narrowing module.
|
|
294
|
+
# Removes the `nil` constituent from a (possibly union) type, returning the same object when
|
|
295
|
+
# there is nothing to remove so callers can detect the no-op cheaply. Kept local to the dispatch
|
|
296
|
+
# tier to avoid a dependency on the narrowing module.
|
|
332
297
|
def strip_nil_constituent(type)
|
|
333
298
|
case type
|
|
334
299
|
when Type::Constant
|
|
@@ -345,17 +310,13 @@ module Rigor
|
|
|
345
310
|
end
|
|
346
311
|
end
|
|
347
312
|
|
|
348
|
-
# `Array[T]#flatten` (and `flatten(depth)`). When `T` is a
|
|
349
|
-
#
|
|
350
|
-
#
|
|
351
|
-
#
|
|
352
|
-
#
|
|
353
|
-
#
|
|
354
|
-
#
|
|
355
|
-
# not fully resolve the nesting still produces a sound
|
|
356
|
-
# superset. Declines on an `Array` with no type argument
|
|
357
|
-
# (the RBS `Array[untyped]` answer is already as precise as
|
|
358
|
-
# we can be) and on a non-static depth argument.
|
|
313
|
+
# `Array[T]#flatten` (and `flatten(depth)`). When `T` is a nested `Array[U]` nominal, one flatten
|
|
314
|
+
# level yields the joined inner element type — `Array[Array[U]]#flatten` → `Array[U]`. When `T`
|
|
315
|
+
# is non-nested the result is `Array[T]` unchanged (Ruby returns a copy with the same element
|
|
316
|
+
# type). Multi-level nesting is handled conservatively: each level joins its element types, and a
|
|
317
|
+
# `depth` argument that does not fully resolve the nesting still produces a sound superset.
|
|
318
|
+
# Declines on an `Array` with no type argument (the RBS `Array[untyped]` answer is already as
|
|
319
|
+
# precise as we can be) and on a non-static depth argument.
|
|
359
320
|
def array_nominal_flatten(nominal, args)
|
|
360
321
|
element = nominal.type_args&.first
|
|
361
322
|
return nil if element.nil?
|
|
@@ -367,11 +328,9 @@ module Rigor
|
|
|
367
328
|
Type::Combinator.nominal_of("Array", type_args: [flattened])
|
|
368
329
|
end
|
|
369
330
|
|
|
370
|
-
# Resolves the element type of a flattened `Array[element]`.
|
|
371
|
-
#
|
|
372
|
-
#
|
|
373
|
-
# bound; `depth == 0` stops (Ruby's `flatten(0)` is a no-op
|
|
374
|
-
# copy and returns the element unchanged).
|
|
331
|
+
# Resolves the element type of a flattened `Array[element]`. Each `Array[U]` nesting level
|
|
332
|
+
# contributes `U`; the per-level element types are unioned. `depth < 0` recurses without bound;
|
|
333
|
+
# `depth == 0` stops (Ruby's `flatten(0)` is a no-op copy and returns the element unchanged).
|
|
375
334
|
def flatten_nominal_element(element, depth)
|
|
376
335
|
return element if depth.zero?
|
|
377
336
|
return element unless array_nominal?(element)
|
|
@@ -387,14 +346,13 @@ module Rigor
|
|
|
387
346
|
!type.type_args.empty?
|
|
388
347
|
end
|
|
389
348
|
|
|
390
|
-
# Arg-type-driven String binary projections for any String-typed
|
|
391
|
-
#
|
|
392
|
-
#
|
|
349
|
+
# Arg-type-driven String binary projections for any String-typed receiver (including Nominal,
|
|
350
|
+
# Refined, and Difference fallbacks). Called before the no-arg size guard so binary operators are
|
|
351
|
+
# seen.
|
|
393
352
|
#
|
|
394
|
-
# - `String + non-empty-string` → `non-empty-string`
|
|
395
|
-
#
|
|
396
|
-
# - `String * Constant[0]` → `Constant[""]`
|
|
397
|
-
# (every string repeated 0 times is the empty string)
|
|
353
|
+
# - `String + non-empty-string` → `non-empty-string` (arg guarantees the concatenation is
|
|
354
|
+
# non-empty)
|
|
355
|
+
# - `String * Constant[0]` → `Constant[""]` (every string repeated 0 times is the empty string)
|
|
398
356
|
def dispatch_string_binary_from_arg(method_name, arg)
|
|
399
357
|
case method_name
|
|
400
358
|
when :+
|
|
@@ -407,11 +365,10 @@ module Rigor
|
|
|
407
365
|
nil
|
|
408
366
|
end
|
|
409
367
|
|
|
410
|
-
# Arg-type-driven Integer binary projections for any Integer-typed
|
|
411
|
-
#
|
|
368
|
+
# Arg-type-driven Integer binary projections for any Integer-typed receiver (including Nominal,
|
|
369
|
+
# Refined, and Difference fallbacks).
|
|
412
370
|
#
|
|
413
|
-
# - `Integer * Constant[0]` → `Constant[0]`
|
|
414
|
-
# (any integer multiplied by 0 is 0)
|
|
371
|
+
# - `Integer * Constant[0]` → `Constant[0]` (any integer multiplied by 0 is 0)
|
|
415
372
|
def dispatch_integer_binary_from_arg(method_name, arg)
|
|
416
373
|
return nil unless method_name == :*
|
|
417
374
|
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer) && arg.value.zero?
|
|
@@ -419,15 +376,12 @@ module Rigor
|
|
|
419
376
|
Type::Combinator.constant_of(0)
|
|
420
377
|
end
|
|
421
378
|
|
|
422
|
-
# `IntegerRange#to_s` precision (v0.1.1 Track 1 slice 5b).
|
|
423
|
-
#
|
|
424
|
-
#
|
|
425
|
-
#
|
|
426
|
-
#
|
|
427
|
-
#
|
|
428
|
-
# for 16). Signed ranges fall through (the result could
|
|
429
|
-
# carry a `-` sign that no Rigor refinement currently
|
|
430
|
-
# captures), as do bases without a digit-only refinement.
|
|
379
|
+
# `IntegerRange#to_s` precision (v0.1.1 Track 1 slice 5b). When the range's lower bound is
|
|
380
|
+
# `>= 0`, every member is a non-negative integer and `to_s(base)` returns a digit-string with no
|
|
381
|
+
# leading sign. The result lifts to the matching imported refinement (`decimal-int-string` for
|
|
382
|
+
# base 10, `octal-int-string` for 8, `hex-int-string` for 16). Signed ranges fall through (the
|
|
383
|
+
# result could carry a `-` sign that no Rigor refinement currently captures), as do bases
|
|
384
|
+
# without a digit-only refinement.
|
|
431
385
|
def dispatch_integer_range(range, method_name, args)
|
|
432
386
|
return nil unless method_name == :to_s
|
|
433
387
|
return nil unless range.lower >= 0
|
|
@@ -441,10 +395,9 @@ module Rigor
|
|
|
441
395
|
Type::Combinator.public_send(refinement)
|
|
442
396
|
end
|
|
443
397
|
|
|
444
|
-
# `to_s` with no argument defaults to base 10. With one
|
|
445
|
-
#
|
|
446
|
-
#
|
|
447
|
-
# arg, multi-arg, etc.) declines.
|
|
398
|
+
# `to_s` with no argument defaults to base 10. With one argument, the value MUST be a
|
|
399
|
+
# `Constant<Integer>` to be statically known. Anything else (Nominal[Integer] arg, multi-arg,
|
|
400
|
+
# etc.) declines.
|
|
448
401
|
def base_argument(args)
|
|
449
402
|
return 10 if args.empty?
|
|
450
403
|
return nil unless args.size == 1
|
|
@@ -455,21 +408,18 @@ module Rigor
|
|
|
455
408
|
arg.value
|
|
456
409
|
end
|
|
457
410
|
|
|
458
|
-
# Refinement-aware projections over a `Difference[base,
|
|
459
|
-
#
|
|
460
|
-
#
|
|
461
|
-
# String, `Tuple[]` for Array, `HashShape{}` for Hash,
|
|
462
|
-
# `Constant[0]` for Integer), the catalog tier knows:
|
|
411
|
+
# Refinement-aware projections over a `Difference[base, removed]` receiver. When the removed
|
|
412
|
+
# value is the empty witness of the base (`Constant[""]` for String, `Tuple[]` for Array,
|
|
413
|
+
# `HashShape{}` for Hash, `Constant[0]` for Integer), the catalog tier knows:
|
|
463
414
|
#
|
|
464
415
|
# ns.size # positive-int
|
|
465
416
|
# ns.size == 0 # Constant[false] (via narrowing tier)
|
|
466
417
|
# ns.empty? # Constant[false]
|
|
467
418
|
# nzi.zero? # Constant[false]
|
|
468
419
|
#
|
|
469
|
-
# For any other base method, the difference is opaque
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
# `non_negative_int` envelope where applicable.
|
|
420
|
+
# For any other base method, the difference is opaque to ShapeDispatch — we delegate to the base
|
|
421
|
+
# nominal so the size/length tier still answers the broader `non_negative_int` envelope where
|
|
422
|
+
# applicable.
|
|
473
423
|
def dispatch_difference(difference, method_name, args)
|
|
474
424
|
base = difference.base
|
|
475
425
|
return nil unless base.is_a?(Type::Nominal)
|
|
@@ -499,12 +449,11 @@ module Rigor
|
|
|
499
449
|
!!(predicate && predicate.call(difference.removed))
|
|
500
450
|
end
|
|
501
451
|
|
|
502
|
-
# Methods on a non-empty String that preserve non-emptiness
|
|
503
|
-
#
|
|
452
|
+
# Methods on a non-empty String that preserve non-emptiness (they transform characters but never
|
|
453
|
+
# reduce the string to "").
|
|
504
454
|
NON_EMPTY_STRING_PRESERVING_UNARY = Set[:upcase, :downcase, :capitalize, :swapcase, :reverse].freeze
|
|
505
|
-
# Methods on non-zero-int that return a non-zero-int (identity ops).
|
|
506
|
-
#
|
|
507
|
-
# identity operations on Integer.
|
|
455
|
+
# Methods on non-zero-int that return a non-zero-int (identity ops). Negation of a non-zero
|
|
456
|
+
# integer is non-zero; `to_i`/`to_int` are identity operations on Integer.
|
|
508
457
|
NON_ZERO_INT_PRESERVING_UNARY = Set[:-@, :+@, :to_i, :to_int].freeze
|
|
509
458
|
private_constant :NON_EMPTY_STRING_PRESERVING_UNARY, :NON_ZERO_INT_PRESERVING_UNARY
|
|
510
459
|
|
|
@@ -593,67 +542,64 @@ module Rigor
|
|
|
593
542
|
Type::Combinator.positive_int
|
|
594
543
|
end
|
|
595
544
|
|
|
596
|
-
# Predicate-subset projections over a `Refined[base,
|
|
597
|
-
#
|
|
598
|
-
#
|
|
599
|
-
# `
|
|
600
|
-
#
|
|
601
|
-
# `lowercase-string` to `uppercase-string`. Symmetric
|
|
602
|
-
# rules apply with the predicates swapped. Numeric-
|
|
603
|
-
# string idempotence over `#downcase` / `#upcase` is
|
|
604
|
-
# also recognised because a numeric string equals its
|
|
545
|
+
# Predicate-subset projections over a `Refined[base, predicate]` receiver. Today the catalogue is
|
|
546
|
+
# the String case-normalisation pair: `s.downcase` over a `lowercase-string` receiver folds to
|
|
547
|
+
# the same carrier (already lowercase), and `s.upcase` lifts a `lowercase-string` to
|
|
548
|
+
# `uppercase-string`. Symmetric rules apply with the predicates swapped. Numeric-string
|
|
549
|
+
# idempotence over `#downcase` / `#upcase` is also recognised because a numeric string equals its
|
|
605
550
|
# own case-normalisation.
|
|
606
551
|
#
|
|
607
|
-
# For methods this tier does not have a refinement-
|
|
608
|
-
#
|
|
609
|
-
# `dispatch_nominal_size` so size-returning calls on
|
|
610
|
-
# a `Refined[String, *]` still tighten to
|
|
552
|
+
# For methods this tier does not have a refinement-specific rule for, projection delegates to
|
|
553
|
+
# `dispatch_nominal_size` so size-returning calls on a `Refined[String, *]` still tighten to
|
|
611
554
|
# `non_negative_int`.
|
|
612
|
-
# ADR-15 Phase 4b.x — `Ractor.make_shareable` (not `.freeze`)
|
|
613
|
-
#
|
|
614
|
-
#
|
|
615
|
-
#
|
|
616
|
-
# the dispatch loop's `REFINED_STRING_PROJECTIONS[[id, sym]]`
|
|
617
|
-
# lookup ran from a worker Ractor.
|
|
555
|
+
# ADR-15 Phase 4b.x — `Ractor.make_shareable` (not `.freeze`) because the keys are two-element
|
|
556
|
+
# Symbol arrays whose inner arrays are unfrozen under shallow `.freeze`. Surfaced on Discourse via
|
|
557
|
+
# `Ractor::IsolationError` when the dispatch loop's `REFINED_STRING_PROJECTIONS[[id, sym]]` lookup
|
|
558
|
+
# ran from a worker Ractor.
|
|
618
559
|
REFINED_STRING_PROJECTIONS = Ractor.make_shareable({
|
|
619
560
|
%i[lowercase downcase] => :refined_self,
|
|
620
561
|
%i[lowercase upcase] => :uppercase_string,
|
|
621
562
|
%i[uppercase upcase] => :refined_self,
|
|
622
563
|
%i[uppercase downcase] => :lowercase_string,
|
|
623
|
-
# `numeric-string` is the full Ruby
|
|
624
|
-
# grammar (since the predicate
|
|
625
|
-
# parser). `#downcase`
|
|
626
|
-
#
|
|
627
|
-
#
|
|
628
|
-
#
|
|
629
|
-
#
|
|
630
|
-
#
|
|
631
|
-
#
|
|
564
|
+
# `numeric-string` is the full Ruby
|
|
565
|
+
# numeric-literal grammar (since the predicate
|
|
566
|
+
# delegates to the parser). `#downcase`
|
|
567
|
+
# preserves it — lowercasing a literal (hex
|
|
568
|
+
# digits, `0X` / `E` prefixes) yields a valid
|
|
569
|
+
# lowercase literal — but `#upcase` does NOT:
|
|
570
|
+
# the rational / imaginary suffixes are
|
|
571
|
+
# lowercase-only (`"1r".upcase == "1R"` is not
|
|
572
|
+
# a literal), so `upcase` drops to the plain
|
|
573
|
+
# base `String` — still sound (the result is a
|
|
574
|
+
# String), just no longer numeric.
|
|
632
575
|
%i[numeric downcase] => :refined_self,
|
|
633
576
|
%i[numeric upcase] => :base_string,
|
|
634
|
-
# Digit-only strings are case-invariant; the
|
|
635
|
-
# letters in `0o…` / `0x…` are accepted
|
|
636
|
-
# predicate in either case so the
|
|
637
|
-
# is preserved across
|
|
638
|
-
#
|
|
577
|
+
# Digit-only strings are case-invariant; the
|
|
578
|
+
# prefix letters in `0o…` / `0x…` are accepted
|
|
579
|
+
# by the predicate in either case so the
|
|
580
|
+
# predicate-subset is preserved across
|
|
581
|
+
# `#downcase` / `#upcase` even though the
|
|
582
|
+
# value-set element changes.
|
|
639
583
|
%i[decimal_int downcase] => :refined_self,
|
|
640
584
|
%i[decimal_int upcase] => :refined_self,
|
|
641
585
|
%i[octal_int downcase] => :refined_self,
|
|
642
586
|
%i[octal_int upcase] => :refined_self,
|
|
643
587
|
%i[hex_int downcase] => :refined_self,
|
|
644
588
|
%i[hex_int upcase] => :refined_self,
|
|
645
|
-
# v0.1.1 Track 1 slice 2 — `to_i` / `to_int` on
|
|
646
|
-
# `decimal-int-string` parses to an
|
|
647
|
-
# carrier is `universal_int`,
|
|
648
|
-
# the predicate
|
|
589
|
+
# v0.1.1 Track 1 slice 2 — `to_i` / `to_int` on
|
|
590
|
+
# a `decimal-int-string` parses to an
|
|
591
|
+
# `Integer`. The carrier is `universal_int`,
|
|
592
|
+
# NOT `non-negative-int`: the predicate
|
|
593
|
+
# `/\A-?\d+\z/` admits a leading sign, so
|
|
649
594
|
# `"-7"` is a valid decimal-int-string and
|
|
650
|
-
# `"-7".to_i == -7 < 0`. `String#to_i` is
|
|
651
|
-
# raises), so the projection is
|
|
652
|
-
# `numeric-string` is
|
|
653
|
-
# `to_i` at all:
|
|
654
|
-
#
|
|
655
|
-
#
|
|
656
|
-
#
|
|
595
|
+
# `"-7".to_i == -7 < 0`. `String#to_i` is
|
|
596
|
+
# total (never raises), so the projection is
|
|
597
|
+
# sound — just signed. `numeric-string` is
|
|
598
|
+
# deliberately NOT projected to `to_i` at all:
|
|
599
|
+
# it now spans the full numeric-literal
|
|
600
|
+
# grammar, so a `"1.5"` / `"2i"` inhabitant
|
|
601
|
+
# has a fractional or non-Integer parse — it
|
|
602
|
+
# falls through to the RBS `Integer`.
|
|
657
603
|
%i[decimal_int to_i] => :universal_int,
|
|
658
604
|
%i[decimal_int to_int] => :universal_int
|
|
659
605
|
})
|
|
@@ -685,27 +631,20 @@ module Rigor
|
|
|
685
631
|
end
|
|
686
632
|
end
|
|
687
633
|
|
|
688
|
-
# Projects a method call over an `Intersection[M1, …]`
|
|
689
|
-
#
|
|
690
|
-
#
|
|
691
|
-
#
|
|
692
|
-
# projections is sound. Combining is best-effort:
|
|
634
|
+
# Projects a method call over an `Intersection[M1, …]` receiver by collecting each member's
|
|
635
|
+
# projection and combining the results. The set-theoretic identity is
|
|
636
|
+
# `M(A ∩ B) ⊆ M(A) ∩ M(B)`, so the meet of the per-member projections is sound. Combining is
|
|
637
|
+
# best-effort:
|
|
693
638
|
#
|
|
694
|
-
# - If every result is a `Type::IntegerRange`, return
|
|
695
|
-
#
|
|
696
|
-
#
|
|
697
|
-
# `
|
|
698
|
-
#
|
|
699
|
-
#
|
|
700
|
-
#
|
|
701
|
-
# - Otherwise return the first non-nil result. A richer
|
|
702
|
-
# meet (e.g. of Difference + Refined results when both
|
|
703
|
-
# project) is left for a future slice; the carrier
|
|
704
|
-
# stays sound because every member's projection is
|
|
705
|
-
# already a superset of the true intersection.
|
|
639
|
+
# - If every result is a `Type::IntegerRange`, return their bounded-integer meet (max of lower
|
|
640
|
+
# bounds, min of upper bounds). This catches the common
|
|
641
|
+
# `(non_empty_string ∩ lowercase_string).size` pattern where one member projects to
|
|
642
|
+
# `positive-int` and the other to `non-negative-int`; the meet is `positive-int`.
|
|
643
|
+
# - Otherwise return the first non-nil result. A richer meet (e.g. of Difference + Refined
|
|
644
|
+
# results when both project) is left for a future slice; the carrier stays sound because every
|
|
645
|
+
# member's projection is already a superset of the true intersection.
|
|
706
646
|
#
|
|
707
|
-
# Returns nil when no member projects, so the caller
|
|
708
|
-
# falls through to the next dispatcher tier.
|
|
647
|
+
# Returns nil when no member projects, so the caller falls through to the next dispatcher tier.
|
|
709
648
|
def dispatch_intersection(intersection, method_name, args)
|
|
710
649
|
results = intersection.members.filter_map do |member|
|
|
711
650
|
ShapeDispatch.try_dispatch(
|
|
@@ -726,15 +665,11 @@ module Rigor
|
|
|
726
665
|
results.first
|
|
727
666
|
end
|
|
728
667
|
|
|
729
|
-
# Compute the bounded-integer meet of two or more
|
|
730
|
-
# `
|
|
731
|
-
#
|
|
732
|
-
#
|
|
733
|
-
#
|
|
734
|
-
# `IntegerRange.new` expects. The disjoint-meet case
|
|
735
|
-
# cannot arise from sound member-wise projections in
|
|
736
|
-
# v0.0.4 but is guarded defensively to keep the
|
|
737
|
-
# carrier total.
|
|
668
|
+
# Compute the bounded-integer meet of two or more `IntegerRange` carriers. We compare via the
|
|
669
|
+
# numeric `lower` / `upper` accessors (`-Float::INFINITY` / `Float::INFINITY` for the symbolic
|
|
670
|
+
# ends), then map back to the symbolic-bound representation `IntegerRange.new` expects. The
|
|
671
|
+
# disjoint-meet case cannot arise from sound member-wise projections in v0.0.4 but is guarded
|
|
672
|
+
# defensively to keep the carrier total.
|
|
738
673
|
def narrow_integer_ranges(ranges)
|
|
739
674
|
numeric_low = ranges.map(&:lower).max
|
|
740
675
|
numeric_high = ranges.map(&:upper).min
|
|
@@ -745,10 +680,9 @@ module Rigor
|
|
|
745
680
|
Type::Combinator.integer_range(min, max)
|
|
746
681
|
end
|
|
747
682
|
|
|
748
|
-
# `first` (no arg) → the first element (or `Constant[nil]` when
|
|
749
|
-
#
|
|
750
|
-
#
|
|
751
|
-
# it here would change that documented `Array[Elem]` contract.
|
|
683
|
+
# `first` (no arg) → the first element (or `Constant[nil]` when empty). The `first(n)` arg-form
|
|
684
|
+
# is deliberately left to RBS overload selection (see the overload-selection specs) — folding it
|
|
685
|
+
# here would change that documented `Array[Elem]` contract.
|
|
752
686
|
def tuple_first(tuple, _method_name, args)
|
|
753
687
|
return nil unless args.empty?
|
|
754
688
|
return Type::Combinator.constant_of(nil) if tuple.elements.empty?
|
|
@@ -769,8 +703,7 @@ module Rigor
|
|
|
769
703
|
Type::Combinator.constant_of(tuple.elements.size)
|
|
770
704
|
end
|
|
771
705
|
|
|
772
|
-
# `tuple.empty?` — folds to a precise bool from the
|
|
773
|
-
# tuple's known arity.
|
|
706
|
+
# `tuple.empty?` — folds to a precise bool from the tuple's known arity.
|
|
774
707
|
# rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
|
775
708
|
def tuple_empty?(tuple, _method_name, args)
|
|
776
709
|
return nil unless args.empty?
|
|
@@ -778,20 +711,17 @@ module Rigor
|
|
|
778
711
|
Type::Combinator.constant_of(tuple.elements.empty?)
|
|
779
712
|
end
|
|
780
713
|
|
|
781
|
-
# `tuple.any?` (no-arg, no-block) — empty tuple → false,
|
|
782
|
-
#
|
|
783
|
-
# `BlockFolding` and the RBS tier.
|
|
714
|
+
# `tuple.any?` (no-arg, no-block) — empty tuple → false, non-empty → true. The block / arg forms
|
|
715
|
+
# flow through `BlockFolding` and the RBS tier.
|
|
784
716
|
def tuple_any?(tuple, _method_name, args)
|
|
785
717
|
return nil unless args.empty?
|
|
786
718
|
|
|
787
719
|
Type::Combinator.constant_of(!tuple.elements.empty?)
|
|
788
720
|
end
|
|
789
721
|
|
|
790
|
-
# `tuple.all?` (no-arg, no-block) — true for empty
|
|
791
|
-
#
|
|
792
|
-
#
|
|
793
|
-
# element truthiness declines so the RBS / BlockFolding
|
|
794
|
-
# tiers can still answer.
|
|
722
|
+
# `tuple.all?` (no-arg, no-block) — true for empty tuple (vacuous truth) AND for non-empty tuples
|
|
723
|
+
# whose every element is provably truthy. Mixed / unknown element truthiness declines so the RBS
|
|
724
|
+
# / BlockFolding tiers can still answer.
|
|
795
725
|
def tuple_all?(tuple, _method_name, args)
|
|
796
726
|
return nil unless args.empty?
|
|
797
727
|
return Type::Combinator.constant_of(true) if tuple.elements.empty?
|
|
@@ -802,9 +732,8 @@ module Rigor
|
|
|
802
732
|
Type::Combinator.constant_of(decision)
|
|
803
733
|
end
|
|
804
734
|
|
|
805
|
-
# `tuple.none?` (no-arg, no-block) — true when every
|
|
806
|
-
# element is provably
|
|
807
|
-
# provably truthy. Empty tuple folds to true (vacuous).
|
|
735
|
+
# `tuple.none?` (no-arg, no-block) — true when every element is provably falsey, false when any
|
|
736
|
+
# element is provably truthy. Empty tuple folds to true (vacuous).
|
|
808
737
|
def tuple_none?(tuple, _method_name, args)
|
|
809
738
|
return nil unless args.empty?
|
|
810
739
|
return Type::Combinator.constant_of(true) if tuple.elements.empty?
|
|
@@ -815,13 +744,10 @@ module Rigor
|
|
|
815
744
|
Type::Combinator.constant_of(decision)
|
|
816
745
|
end
|
|
817
746
|
|
|
818
|
-
# `tuple.include?(needle)` — folds to a precise bool when
|
|
819
|
-
#
|
|
820
|
-
#
|
|
821
|
-
#
|
|
822
|
-
# is `Constant[true]`; if every element is a Constant
|
|
823
|
-
# whose value is structurally distinct from the needle
|
|
824
|
-
# the answer is `Constant[false]`.
|
|
747
|
+
# `tuple.include?(needle)` — folds to a precise bool when the needle is a `Constant` and the
|
|
748
|
+
# tuple's elements are all `Constant` (so disjointness is checkable). If any element matches the
|
|
749
|
+
# needle's value the answer is `Constant[true]`; if every element is a Constant whose value is
|
|
750
|
+
# structurally distinct from the needle the answer is `Constant[false]`.
|
|
825
751
|
def tuple_include?(tuple, _method_name, args)
|
|
826
752
|
return nil unless args.size == 1
|
|
827
753
|
|
|
@@ -836,9 +762,8 @@ module Rigor
|
|
|
836
762
|
end
|
|
837
763
|
# rubocop:enable Style/ReturnNilInPredicateMethodDefinition
|
|
838
764
|
|
|
839
|
-
# `tuple.sum` — when every element is a numeric Constant,
|
|
840
|
-
#
|
|
841
|
-
# decline so RBS widens.
|
|
765
|
+
# `tuple.sum` — when every element is a numeric Constant, fold to `Constant[sum]`. Mixed /
|
|
766
|
+
# non-numeric elements decline so RBS widens.
|
|
842
767
|
def tuple_sum(tuple, _method_name, args)
|
|
843
768
|
return nil unless args.empty?
|
|
844
769
|
return Type::Combinator.constant_of(0) if tuple.elements.empty?
|
|
@@ -849,10 +774,9 @@ module Rigor
|
|
|
849
774
|
Type::Combinator.constant_of(values.sum)
|
|
850
775
|
end
|
|
851
776
|
|
|
852
|
-
# `tuple.join(sep = "")` — fold to the joined `Constant[String]`
|
|
853
|
-
#
|
|
854
|
-
#
|
|
855
|
-
# `Constant[String]`. Capped at `TUPLE_JOIN_BYTE_LIMIT`.
|
|
777
|
+
# `tuple.join(sep = "")` — fold to the joined `Constant[String]` when every element is a
|
|
778
|
+
# `Constant` (its `to_s` is deterministic for the scalar value classes) and the separator is
|
|
779
|
+
# absent or a `Constant[String]`. Capped at `TUPLE_JOIN_BYTE_LIMIT`.
|
|
856
780
|
def tuple_join(tuple, _method_name, args)
|
|
857
781
|
sep = tuple_join_separator(args)
|
|
858
782
|
return nil if sep.nil?
|
|
@@ -868,8 +792,8 @@ module Rigor
|
|
|
868
792
|
nil
|
|
869
793
|
end
|
|
870
794
|
|
|
871
|
-
# The join separator: `""` for the no-arg form, the value of a
|
|
872
|
-
#
|
|
795
|
+
# The join separator: `""` for the no-arg form, the value of a single `Constant[String]` arg, or
|
|
796
|
+
# `nil` to decline.
|
|
873
797
|
def tuple_join_separator(args)
|
|
874
798
|
return "" if args.empty?
|
|
875
799
|
return nil unless args.size == 1
|
|
@@ -880,12 +804,10 @@ module Rigor
|
|
|
880
804
|
arg.value
|
|
881
805
|
end
|
|
882
806
|
|
|
883
|
-
# `tuple.min` / `tuple.max` — fold when every element is
|
|
884
|
-
#
|
|
885
|
-
#
|
|
886
|
-
#
|
|
887
|
-
# edge-most values in Ruby's order (`min(n)` ascending,
|
|
888
|
-
# `max(n)` descending) — the n-arg sibling of `first(n)`.
|
|
807
|
+
# `tuple.min` / `tuple.max` — fold when every element is a `Constant` whose values share a
|
|
808
|
+
# Ruby-comparable domain. Empty tuples fold to `Constant[nil]`. The 1-arg `min(n)` / `max(n)`
|
|
809
|
+
# form folds to a `Tuple` of the n edge-most values in Ruby's order (`min(n)` ascending, `max(n)`
|
|
810
|
+
# descending) — the n-arg sibling of `first(n)`.
|
|
889
811
|
def tuple_min(tuple, _method_name, args)
|
|
890
812
|
tuple_minmax(tuple, args, :min)
|
|
891
813
|
end
|
|
@@ -907,12 +829,10 @@ module Rigor
|
|
|
907
829
|
nil
|
|
908
830
|
end
|
|
909
831
|
|
|
910
|
-
# `tuple.min(n)` / `tuple.max(n)` — a `Tuple` of the n
|
|
911
|
-
#
|
|
912
|
-
#
|
|
913
|
-
#
|
|
914
|
-
# The result is bounded by the tuple's known arity, so no
|
|
915
|
-
# extra size cap is needed.
|
|
832
|
+
# `tuple.min(n)` / `tuple.max(n)` — a `Tuple` of the n edge-most element values, delegating to
|
|
833
|
+
# Ruby's `Array#min` / `#max` for the ordering. Declines on a non-static / negative count or
|
|
834
|
+
# non-Constant elements. The result is bounded by the tuple's known arity, so no extra size cap
|
|
835
|
+
# is needed.
|
|
916
836
|
def tuple_minmax_n(tuple, arg, edge)
|
|
917
837
|
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer)
|
|
918
838
|
return nil if arg.value.negative?
|
|
@@ -926,11 +846,9 @@ module Rigor
|
|
|
926
846
|
nil
|
|
927
847
|
end
|
|
928
848
|
|
|
929
|
-
# `tuple.minmax` — the `[min, max]` pair as a 2-slot
|
|
930
|
-
# `
|
|
931
|
-
#
|
|
932
|
-
# and the values must Ruby-compare; an empty tuple folds to
|
|
933
|
-
# `Tuple[nil, nil]` (Ruby's `[].minmax`), incomparable
|
|
849
|
+
# `tuple.minmax` — the `[min, max]` pair as a 2-slot `Tuple[Constant[min], Constant[max]]`,
|
|
850
|
+
# mirroring the `Range#minmax` fold. Every element must be a `Constant` and the values must
|
|
851
|
+
# Ruby-compare; an empty tuple folds to `Tuple[nil, nil]` (Ruby's `[].minmax`), incomparable
|
|
934
852
|
# mixed-class values decline.
|
|
935
853
|
def tuple_minmax_pair(tuple, _method_name, args)
|
|
936
854
|
return nil unless args.empty?
|
|
@@ -952,10 +870,9 @@ module Rigor
|
|
|
952
870
|
nil
|
|
953
871
|
end
|
|
954
872
|
|
|
955
|
-
# `tuple.sort` — every element must be a `Constant` and
|
|
956
|
-
# the
|
|
957
|
-
#
|
|
958
|
-
# failures (mixed-class incomparable values) decline.
|
|
873
|
+
# `tuple.sort` — every element must be a `Constant` and the values must Ruby-compare. The result
|
|
874
|
+
# is a Tuple with the same elements in sorted order. Comparison failures (mixed-class
|
|
875
|
+
# incomparable values) decline.
|
|
959
876
|
def tuple_sort(tuple, _method_name, args)
|
|
960
877
|
return nil unless args.empty?
|
|
961
878
|
return tuple if tuple.elements.size <= 1
|
|
@@ -969,32 +886,27 @@ module Rigor
|
|
|
969
886
|
nil
|
|
970
887
|
end
|
|
971
888
|
|
|
972
|
-
# `tuple.reverse` — independent of element shape; a
|
|
973
|
-
# tuple-precise reversed Tuple.
|
|
889
|
+
# `tuple.reverse` — independent of element shape; a tuple-precise reversed Tuple.
|
|
974
890
|
def tuple_reverse(tuple, _method_name, args)
|
|
975
891
|
return nil unless args.empty?
|
|
976
892
|
|
|
977
893
|
Type::Combinator.tuple_of(*tuple.elements.reverse)
|
|
978
894
|
end
|
|
979
895
|
|
|
980
|
-
# `tuple.to_a` — Tuple is structurally identical to its
|
|
981
|
-
#
|
|
896
|
+
# `tuple.to_a` — Tuple is structurally identical to its to_a (Ruby returns the receiver itself
|
|
897
|
+
# for an Array).
|
|
982
898
|
def tuple_to_a(tuple, _method_name, args)
|
|
983
899
|
return nil unless args.empty?
|
|
984
900
|
|
|
985
901
|
tuple
|
|
986
902
|
end
|
|
987
903
|
|
|
988
|
-
# `tuple.zip(other_1, other_2, …)` — pairs the receiver's
|
|
989
|
-
# per-position elements
|
|
990
|
-
#
|
|
991
|
-
# of
|
|
992
|
-
# `
|
|
993
|
-
#
|
|
994
|
-
# `Constant[nil]` (matching Ruby's runtime semantics).
|
|
995
|
-
# Declines when any `other_k` is not a Tuple, since the
|
|
996
|
-
# arity is then unknown and the result would be
|
|
997
|
-
# `Array[Array[…]]` — RBS already gives that answer.
|
|
904
|
+
# `tuple.zip(other_1, other_2, …)` — pairs the receiver's per-position elements with the
|
|
905
|
+
# per-position elements of each other Tuple-shaped argument. The result is a Tuple of Tuples
|
|
906
|
+
# whose arity matches the receiver: position `i` is `Tuple[receiver[i], other_1[i], other_2[i],
|
|
907
|
+
# …]`. Out-of-range positions in any `other_k` contribute `Constant[nil]` (matching Ruby's
|
|
908
|
+
# runtime semantics). Declines when any `other_k` is not a Tuple, since the arity is then unknown
|
|
909
|
+
# and the result would be `Array[Array[…]]` — RBS already gives that answer.
|
|
998
910
|
def tuple_zip(tuple, _method_name, args)
|
|
999
911
|
return nil if args.empty? || args.size > MAX_ZIP_ARITY
|
|
1000
912
|
return nil unless args.all?(Type::Tuple)
|
|
@@ -1009,11 +921,9 @@ module Rigor
|
|
|
1009
921
|
MAX_ZIP_ARITY = 8
|
|
1010
922
|
private_constant :MAX_ZIP_ARITY
|
|
1011
923
|
|
|
1012
|
-
# `tuple.to_h` — folds when every Tuple element is itself
|
|
1013
|
-
# a
|
|
1014
|
-
#
|
|
1015
|
-
# `HashShape` whose entries mirror the per-position
|
|
1016
|
-
# pairs. Empty Tuples fold to the empty HashShape.
|
|
924
|
+
# `tuple.to_h` — folds when every Tuple element is itself a 2-element Tuple whose first element
|
|
925
|
+
# is a `Constant` (so it can serve as a Hash key). Produces a closed `HashShape` whose entries
|
|
926
|
+
# mirror the per-position pairs. Empty Tuples fold to the empty HashShape.
|
|
1017
927
|
def tuple_to_h(tuple, _method_name, args)
|
|
1018
928
|
return nil unless args.empty?
|
|
1019
929
|
return Type::Combinator.hash_shape_of({}) if tuple.elements.empty?
|
|
@@ -1036,11 +946,9 @@ module Rigor
|
|
|
1036
946
|
[key.value, value]
|
|
1037
947
|
end
|
|
1038
948
|
|
|
1039
|
-
# `tuple.values_at(i1, i2, ...)` — returns a Tuple of
|
|
1040
|
-
#
|
|
1041
|
-
#
|
|
1042
|
-
# `Constant[nil]`, mirroring Ruby's runtime behaviour.
|
|
1043
|
-
# Declines when any argument is non-static.
|
|
949
|
+
# `tuple.values_at(i1, i2, ...)` — returns a Tuple of per-index elements. Each argument must be a
|
|
950
|
+
# `Constant[Integer]`. Out-of-range indices fill with `Constant[nil]`, mirroring Ruby's runtime
|
|
951
|
+
# behaviour. Declines when any argument is non-static.
|
|
1044
952
|
def tuple_values_at(tuple, _method_name, args)
|
|
1045
953
|
return nil if args.empty?
|
|
1046
954
|
|
|
@@ -1055,10 +963,8 @@ module Rigor
|
|
|
1055
963
|
Type::Combinator.tuple_of(*values)
|
|
1056
964
|
end
|
|
1057
965
|
|
|
1058
|
-
# `tuple + other` — concatenates two Tuples. Both sides
|
|
1059
|
-
#
|
|
1060
|
-
# elements are those of the receiver followed by those
|
|
1061
|
-
# of the argument.
|
|
966
|
+
# `tuple + other` — concatenates two Tuples. Both sides must be `Type::Tuple`. Returns a new
|
|
967
|
+
# Tuple whose elements are those of the receiver followed by those of the argument.
|
|
1062
968
|
def tuple_concat(tuple, _method_name, args)
|
|
1063
969
|
return nil unless args.size == 1
|
|
1064
970
|
|
|
@@ -1068,10 +974,9 @@ module Rigor
|
|
|
1068
974
|
Type::Combinator.tuple_of(*tuple.elements, *other.elements)
|
|
1069
975
|
end
|
|
1070
976
|
|
|
1071
|
-
# `tuple.compact` — removes every element that is
|
|
1072
|
-
# `Constant
|
|
1073
|
-
#
|
|
1074
|
-
# elements decline so the RBS tier widens.
|
|
977
|
+
# `tuple.compact` — removes every element that is `Constant[nil]`. Folds only when every element
|
|
978
|
+
# is a `Constant` (so the nil set is decidable). Mixed-shape elements decline so the RBS tier
|
|
979
|
+
# widens.
|
|
1075
980
|
def tuple_compact(tuple, _method_name, args)
|
|
1076
981
|
return nil unless args.empty?
|
|
1077
982
|
return nil unless tuple.elements.all?(Type::Constant)
|
|
@@ -1080,9 +985,8 @@ module Rigor
|
|
|
1080
985
|
Type::Combinator.tuple_of(*kept)
|
|
1081
986
|
end
|
|
1082
987
|
|
|
1083
|
-
# `uniq` (no block) → `Tuple` of the first occurrence of each
|
|
1084
|
-
#
|
|
1085
|
-
# so value equality is decidable; the block form defers.
|
|
988
|
+
# `uniq` (no block) → `Tuple` of the first occurrence of each distinct value. Folds only when
|
|
989
|
+
# every element is a `Constant` so value equality is decidable; the block form defers.
|
|
1086
990
|
def tuple_uniq(tuple, _method_name, args)
|
|
1087
991
|
return nil unless args.empty?
|
|
1088
992
|
return nil unless tuple.elements.all?(Type::Constant)
|
|
@@ -1097,22 +1001,18 @@ module Rigor
|
|
|
1097
1001
|
Type::Combinator.tuple_of(*kept)
|
|
1098
1002
|
end
|
|
1099
1003
|
|
|
1100
|
-
# `index(obj)` / `find_index(obj)` → `Constant[Integer]` of the
|
|
1101
|
-
#
|
|
1102
|
-
#
|
|
1103
|
-
# every element AND the argument are `Constant` (decidable
|
|
1104
|
-
# equality).
|
|
1004
|
+
# `index(obj)` / `find_index(obj)` → `Constant[Integer]` of the first element equal to `obj`,
|
|
1005
|
+
# `Constant[nil]` when none match. Folds only for the argument form (the block form defers) when
|
|
1006
|
+
# every element AND the argument are `Constant` (decidable equality).
|
|
1105
1007
|
def tuple_find_index(tuple, _method_name, args)
|
|
1106
1008
|
constant_index(tuple, args) { |elements, value| elements.index { |e| e.value == value } }
|
|
1107
1009
|
end
|
|
1108
1010
|
|
|
1109
|
-
# `tuple.flatten` / `tuple.flatten(depth)` — recursively
|
|
1110
|
-
#
|
|
1111
|
-
#
|
|
1112
|
-
#
|
|
1113
|
-
#
|
|
1114
|
-
# through unchanged at their level. A non-static depth
|
|
1115
|
-
# argument (or a non-Integer one) declines so RBS answers.
|
|
1011
|
+
# `tuple.flatten` / `tuple.flatten(depth)` — recursively flattens nested Tuple elements into a
|
|
1012
|
+
# single Tuple. With no argument the flatten is unbounded (matching Ruby's `Array#flatten`); a
|
|
1013
|
+
# `Constant[Integer]` depth bounds it. Non-Tuple elements (scalars, `Array[T]` nominals, …) pass
|
|
1014
|
+
# through unchanged at their level. A non-static depth argument (or a non-Integer one) declines
|
|
1015
|
+
# so RBS answers.
|
|
1116
1016
|
def tuple_flatten(tuple, _method_name, args)
|
|
1117
1017
|
depth = tuple_flatten_depth(args)
|
|
1118
1018
|
return nil if depth == :decline
|
|
@@ -1120,10 +1020,8 @@ module Rigor
|
|
|
1120
1020
|
Type::Combinator.tuple_of(*flatten_elements(tuple.elements, depth))
|
|
1121
1021
|
end
|
|
1122
1022
|
|
|
1123
|
-
# Returns the requested flatten depth: `-1` for the no-arg
|
|
1124
|
-
#
|
|
1125
|
-
# argument, or `:decline` for any non-static / wrong-arity
|
|
1126
|
-
# argument shape.
|
|
1023
|
+
# Returns the requested flatten depth: `-1` for the no-arg (unbounded) form, the Integer for a
|
|
1024
|
+
# `Constant[Integer]` argument, or `:decline` for any non-static / wrong-arity argument shape.
|
|
1127
1025
|
def tuple_flatten_depth(args)
|
|
1128
1026
|
return -1 if args.empty?
|
|
1129
1027
|
return :decline unless args.size == 1
|
|
@@ -1134,10 +1032,9 @@ module Rigor
|
|
|
1134
1032
|
:decline
|
|
1135
1033
|
end
|
|
1136
1034
|
|
|
1137
|
-
# Flattens a list of element types to `depth` levels.
|
|
1138
|
-
# `depth
|
|
1139
|
-
#
|
|
1140
|
-
# through at this level.
|
|
1035
|
+
# Flattens a list of element types to `depth` levels. `depth < 0` means unbounded. A Tuple
|
|
1036
|
+
# element is spliced in (recursing with `depth - 1`); everything else passes through at this
|
|
1037
|
+
# level.
|
|
1141
1038
|
def flatten_elements(elements, depth)
|
|
1142
1039
|
return elements if depth.zero?
|
|
1143
1040
|
|
|
@@ -1165,10 +1062,8 @@ module Rigor
|
|
|
1165
1062
|
Type::Combinator.constant_of(yield(tuple.elements, needle.value))
|
|
1166
1063
|
end
|
|
1167
1064
|
|
|
1168
|
-
# `tuple.take(n)` — returns the first n elements as a
|
|
1169
|
-
#
|
|
1170
|
-
# n <= 0 returns the empty Tuple; n >= size returns the
|
|
1171
|
-
# full receiver.
|
|
1065
|
+
# `tuple.take(n)` — returns the first n elements as a new Tuple. The argument must be a
|
|
1066
|
+
# `Constant[Integer]`. n <= 0 returns the empty Tuple; n >= size returns the full receiver.
|
|
1172
1067
|
def tuple_take(tuple, _method_name, args)
|
|
1173
1068
|
n = non_negative_count_arg(args)
|
|
1174
1069
|
return nil if n.nil?
|
|
@@ -1176,8 +1071,8 @@ module Rigor
|
|
|
1176
1071
|
Type::Combinator.tuple_of(*tuple.elements.take(n))
|
|
1177
1072
|
end
|
|
1178
1073
|
|
|
1179
|
-
# `drop(n)` → `Tuple` of every element after the first `n`
|
|
1180
|
-
#
|
|
1074
|
+
# `drop(n)` → `Tuple` of every element after the first `n` (mirror of `take`; `n >= size` → empty
|
|
1075
|
+
# Tuple).
|
|
1181
1076
|
def tuple_drop(tuple, _method_name, args)
|
|
1182
1077
|
n = non_negative_count_arg(args)
|
|
1183
1078
|
return nil if n.nil?
|
|
@@ -1185,9 +1080,8 @@ module Rigor
|
|
|
1185
1080
|
Type::Combinator.tuple_of(*tuple.elements.drop(n))
|
|
1186
1081
|
end
|
|
1187
1082
|
|
|
1188
|
-
# `rotate` (no arg → 1) / `rotate(n)` → `Tuple` of the elements
|
|
1189
|
-
#
|
|
1190
|
-
# and out-of-range `n` by modulo, so any Integer arg folds).
|
|
1083
|
+
# `rotate` (no arg → 1) / `rotate(n)` → `Tuple` of the elements cyclically shifted left by `n`
|
|
1084
|
+
# (`Array#rotate` handles negative and out-of-range `n` by modulo, so any Integer arg folds).
|
|
1191
1085
|
def tuple_rotate(tuple, _method_name, args)
|
|
1192
1086
|
count =
|
|
1193
1087
|
if args.empty?
|
|
@@ -1201,10 +1095,9 @@ module Rigor
|
|
|
1201
1095
|
Type::Combinator.tuple_of(*tuple.elements.rotate(count))
|
|
1202
1096
|
end
|
|
1203
1097
|
|
|
1204
|
-
# Unwraps a single non-negative `Constant[Integer]` count argument
|
|
1205
|
-
#
|
|
1206
|
-
# Integer, or
|
|
1207
|
-
# or negative — `Array#take`/`#drop` raise on negative counts).
|
|
1098
|
+
# Unwraps a single non-negative `Constant[Integer]` count argument (the `take` / `drop` /
|
|
1099
|
+
# `first(n)` / `last(n)` shape). Returns the Integer, or nil to defer (wrong arity, non-constant,
|
|
1100
|
+
# non-Integer, or negative — `Array#take`/`#drop` raise on negative counts).
|
|
1208
1101
|
def non_negative_count_arg(args)
|
|
1209
1102
|
return nil unless args.size == 1
|
|
1210
1103
|
|
|
@@ -1215,10 +1108,8 @@ module Rigor
|
|
|
1215
1108
|
arg.value
|
|
1216
1109
|
end
|
|
1217
1110
|
|
|
1218
|
-
# Returns `true` / `false` if every element's truthiness
|
|
1219
|
-
#
|
|
1220
|
-
# checks every element is truthy; `all: false` checks
|
|
1221
|
-
# every element is falsey.
|
|
1111
|
+
# Returns `true` / `false` if every element's truthiness agrees, nil for mixed-or-unknown shapes.
|
|
1112
|
+
# `all: true` checks every element is truthy; `all: false` checks every element is falsey.
|
|
1222
1113
|
def tuple_predicate_truthiness(tuple, all:)
|
|
1223
1114
|
samples = tuple.elements.map { |e| element_truthiness(e) }
|
|
1224
1115
|
return nil if samples.any?(:unknown)
|
|
@@ -1241,9 +1132,8 @@ module Rigor
|
|
|
1241
1132
|
elements.any? { |e| e.is_a?(Type::Constant) && e.value == value }
|
|
1242
1133
|
end
|
|
1243
1134
|
|
|
1244
|
-
# Per-element Constant value extraction. Returns nil
|
|
1245
|
-
#
|
|
1246
|
-
# decline.
|
|
1135
|
+
# Per-element Constant value extraction. Returns nil when any element is non-Constant, so the
|
|
1136
|
+
# caller can decline.
|
|
1247
1137
|
def constant_values(elements)
|
|
1248
1138
|
return nil unless elements.all?(Type::Constant)
|
|
1249
1139
|
|
|
@@ -1258,15 +1148,12 @@ module Rigor
|
|
|
1258
1148
|
values
|
|
1259
1149
|
end
|
|
1260
1150
|
|
|
1261
|
-
# `tuple[i]`, `tuple[range]`, `tuple[start, length]`, and
|
|
1262
|
-
#
|
|
1263
|
-
#
|
|
1264
|
-
# `
|
|
1265
|
-
#
|
|
1266
|
-
#
|
|
1267
|
-
# start-length folding. `fetch` routes here too but stays
|
|
1268
|
-
# integer-index-only: the Range and start-length branches gate
|
|
1269
|
-
# on this selector set, which `fetch` is deliberately not in.
|
|
1151
|
+
# `tuple[i]`, `tuple[range]`, `tuple[start, length]`, and `tuple.fetch(i)` for static arguments.
|
|
1152
|
+
# Out-of-range single indices still fall through because the same handler serves `fetch`, while
|
|
1153
|
+
# statically nil slices can be represented precisely for `[]`.
|
|
1154
|
+
# `[]` and its exact alias `slice` share the index / Range / start-length folding. `fetch` routes
|
|
1155
|
+
# here too but stays integer-index-only: the Range and start-length branches gate on this
|
|
1156
|
+
# selector set, which `fetch` is deliberately not in.
|
|
1270
1157
|
SLICE_SELECTORS = Set[:[], :slice].freeze
|
|
1271
1158
|
private_constant :SLICE_SELECTORS
|
|
1272
1159
|
|
|
@@ -1316,14 +1203,11 @@ module Rigor
|
|
|
1316
1203
|
[range.begin, range.end].all? { |endpoint| endpoint.nil? || endpoint.is_a?(Integer) }
|
|
1317
1204
|
end
|
|
1318
1205
|
|
|
1319
|
-
# `tuple.dig(i, ...)` with a chain of static keys/indices.
|
|
1320
|
-
#
|
|
1321
|
-
#
|
|
1322
|
-
#
|
|
1323
|
-
# the
|
|
1324
|
-
# short-circuit on nil). Anything else along the chain
|
|
1325
|
-
# falls through to the projection answer so the analyzer
|
|
1326
|
-
# never invents a value it cannot prove.
|
|
1206
|
+
# `tuple.dig(i, ...)` with a chain of static keys/indices. Each step recurses through the
|
|
1207
|
+
# resolved member: a Tuple member dispatches `dig` on the remaining args, a HashShape member
|
|
1208
|
+
# does the same, and a `Constant[nil]` member ends the chain at `Constant[nil]` (matching Ruby's
|
|
1209
|
+
# `Array#dig` short-circuit on nil). Anything else along the chain falls through to the
|
|
1210
|
+
# projection answer so the analyzer never invents a value it cannot prove.
|
|
1327
1211
|
def tuple_dig(tuple, _method_name, args)
|
|
1328
1212
|
return nil if args.empty?
|
|
1329
1213
|
|
|
@@ -1343,8 +1227,8 @@ module Rigor
|
|
|
1343
1227
|
tuple.elements[idx]
|
|
1344
1228
|
end
|
|
1345
1229
|
|
|
1346
|
-
# Returns the in-bounds non-negative index, or nil when the
|
|
1347
|
-
#
|
|
1230
|
+
# Returns the in-bounds non-negative index, or nil when the raw index falls outside
|
|
1231
|
+
# `[-size, size)`.
|
|
1348
1232
|
def normalise_index(raw, size)
|
|
1349
1233
|
adjusted = raw.negative? ? raw + size : raw
|
|
1350
1234
|
return nil if adjusted.negative? || adjusted >= size
|
|
@@ -1360,13 +1244,10 @@ module Rigor
|
|
|
1360
1244
|
Type::Combinator.constant_of(shape.pairs.size)
|
|
1361
1245
|
end
|
|
1362
1246
|
|
|
1363
|
-
# `shape.empty?` — folds to a precise bool when the
|
|
1364
|
-
#
|
|
1365
|
-
#
|
|
1366
|
-
#
|
|
1367
|
-
# `Type::t | nil` (nil signals "no rule, defer to next
|
|
1368
|
-
# tier") so the standard predicate-return rubocop rule
|
|
1369
|
-
# does not apply.
|
|
1247
|
+
# `shape.empty?` — folds to a precise bool when the shape's emptiness is statically known. Closed
|
|
1248
|
+
# shapes with no optional keys have a fixed size, so empty? is `Constant[shape.pairs.empty?]`.
|
|
1249
|
+
# The handler returns `Type::t | nil` (nil signals "no rule, defer to next tier") so the standard
|
|
1250
|
+
# predicate-return rubocop rule does not apply.
|
|
1370
1251
|
# rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
|
1371
1252
|
def hash_empty?(shape, _method_name, args)
|
|
1372
1253
|
return nil unless args.empty?
|
|
@@ -1376,9 +1257,8 @@ module Rigor
|
|
|
1376
1257
|
Type::Combinator.constant_of(shape.pairs.empty?)
|
|
1377
1258
|
end
|
|
1378
1259
|
|
|
1379
|
-
# `shape.any?` (no block, no arg) — opposite of
|
|
1380
|
-
#
|
|
1381
|
-
# RBS / BlockFolding tier.
|
|
1260
|
+
# `shape.any?` (no block, no arg) — opposite of `empty?`. The block / arg forms are answered by
|
|
1261
|
+
# the RBS / BlockFolding tier.
|
|
1382
1262
|
def hash_any?(shape, _method_name, args)
|
|
1383
1263
|
return nil unless args.empty?
|
|
1384
1264
|
return nil unless shape.closed?
|
|
@@ -1387,9 +1267,8 @@ module Rigor
|
|
|
1387
1267
|
Type::Combinator.constant_of(!shape.pairs.empty?)
|
|
1388
1268
|
end
|
|
1389
1269
|
|
|
1390
|
-
# `shape.none?` (no block, no arg) — mirror of `any?`.
|
|
1391
|
-
#
|
|
1392
|
-
# shapes with no optional keys.
|
|
1270
|
+
# `shape.none?` (no block, no arg) — mirror of `any?`. Folds to `Constant[shape.pairs.empty?]`
|
|
1271
|
+
# for closed shapes with no optional keys.
|
|
1393
1272
|
def hash_none?(shape, _method_name, args)
|
|
1394
1273
|
return nil unless args.empty?
|
|
1395
1274
|
return nil unless shape.closed?
|
|
@@ -1398,8 +1277,7 @@ module Rigor
|
|
|
1398
1277
|
Type::Combinator.constant_of(shape.pairs.empty?)
|
|
1399
1278
|
end
|
|
1400
1279
|
|
|
1401
|
-
# `shape.one?` (no block, no arg) — folds to
|
|
1402
|
-
# `Constant[shape.pairs.size == 1]` for a closed shape
|
|
1280
|
+
# `shape.one?` (no block, no arg) — folds to `Constant[shape.pairs.size == 1]` for a closed shape
|
|
1403
1281
|
# with no optional keys.
|
|
1404
1282
|
def hash_one?(shape, _method_name, args)
|
|
1405
1283
|
return nil unless args.empty?
|
|
@@ -1409,19 +1287,17 @@ module Rigor
|
|
|
1409
1287
|
Type::Combinator.constant_of(shape.pairs.size == 1)
|
|
1410
1288
|
end
|
|
1411
1289
|
|
|
1412
|
-
# `shape.deconstruct_keys(keys)` — Ruby's `Hash#deconstruct_keys`
|
|
1413
|
-
#
|
|
1414
|
-
# argument, so the precise answer is the shape unchanged.
|
|
1290
|
+
# `shape.deconstruct_keys(keys)` — Ruby's `Hash#deconstruct_keys` returns the receiver itself
|
|
1291
|
+
# regardless of the `keys` argument, so the precise answer is the shape unchanged.
|
|
1415
1292
|
def hash_deconstruct_keys(shape, _method_name, args)
|
|
1416
1293
|
return nil unless args.size == 1
|
|
1417
1294
|
|
|
1418
1295
|
shape
|
|
1419
1296
|
end
|
|
1420
1297
|
|
|
1421
|
-
# `shape.fetch_values(:a, :b, ...)` — like `values_at` but
|
|
1422
|
-
#
|
|
1423
|
-
#
|
|
1424
|
-
# declines so the RBS tier reflects the raise.
|
|
1298
|
+
# `shape.fetch_values(:a, :b, ...)` — like `values_at` but raises `KeyError` on a missing key.
|
|
1299
|
+
# Folds to `Tuple[V…]` only when every requested key is present; a missing key declines so the
|
|
1300
|
+
# RBS tier reflects the raise.
|
|
1425
1301
|
def hash_fetch_values(shape, _method_name, args)
|
|
1426
1302
|
return nil if args.empty?
|
|
1427
1303
|
return nil unless shape.closed?
|
|
@@ -1440,8 +1316,8 @@ module Rigor
|
|
|
1440
1316
|
Type::Combinator.tuple_of(*values)
|
|
1441
1317
|
end
|
|
1442
1318
|
|
|
1443
|
-
# `shape.assoc(key)` — returns `Tuple[Constant[k], V]` for a
|
|
1444
|
-
#
|
|
1319
|
+
# `shape.assoc(key)` — returns `Tuple[Constant[k], V]` for a known key, `Constant[nil]` for a
|
|
1320
|
+
# missing key.
|
|
1445
1321
|
def hash_assoc(shape, _method_name, args)
|
|
1446
1322
|
return nil unless args.size == 1
|
|
1447
1323
|
return nil unless shape.closed?
|
|
@@ -1457,11 +1333,9 @@ module Rigor
|
|
|
1457
1333
|
Type::Combinator.tuple_of(Type::Combinator.constant_of(key), shape.pairs[key])
|
|
1458
1334
|
end
|
|
1459
1335
|
|
|
1460
|
-
# `shape.rassoc(value)` — reverse of `assoc`: returns
|
|
1461
|
-
# `
|
|
1462
|
-
#
|
|
1463
|
-
# value is a `Constant` so equality is decidable (mirrors
|
|
1464
|
-
# `hash_key`, which returns only the key).
|
|
1336
|
+
# `shape.rassoc(value)` — reverse of `assoc`: returns `Tuple[Constant[k], V]` for the first key
|
|
1337
|
+
# whose VALUE equals the argument, `Constant[nil]` when none match. Folds when every value is a
|
|
1338
|
+
# `Constant` so equality is decidable (mirrors `hash_key`, which returns only the key).
|
|
1465
1339
|
def hash_rassoc(shape, _method_name, args)
|
|
1466
1340
|
return nil unless args.size == 1
|
|
1467
1341
|
return nil unless shape.closed?
|
|
@@ -1477,10 +1351,9 @@ module Rigor
|
|
|
1477
1351
|
Type::Combinator.tuple_of(Type::Combinator.constant_of(pair.first), pair.last)
|
|
1478
1352
|
end
|
|
1479
1353
|
|
|
1480
|
-
# `shape.key(value)` — reverse lookup. Folds when every
|
|
1481
|
-
#
|
|
1482
|
-
#
|
|
1483
|
-
# when no value matches.
|
|
1354
|
+
# `shape.key(value)` — reverse lookup. Folds when every value is a `Constant` so equality is
|
|
1355
|
+
# decidable: returns `Constant[k]` for the first matching key, `Constant[nil]` when no value
|
|
1356
|
+
# matches.
|
|
1484
1357
|
def hash_key(shape, _method_name, args)
|
|
1485
1358
|
return nil unless args.size == 1
|
|
1486
1359
|
return nil unless shape.closed?
|
|
@@ -1494,10 +1367,8 @@ module Rigor
|
|
|
1494
1367
|
Type::Combinator.constant_of(pair&.first)
|
|
1495
1368
|
end
|
|
1496
1369
|
|
|
1497
|
-
# `shape.has_value?(v)` / `value?(v)` — folds to
|
|
1498
|
-
# `Constant
|
|
1499
|
-
# (so equality is decidable) and the argument is a
|
|
1500
|
-
# `Constant`.
|
|
1370
|
+
# `shape.has_value?(v)` / `value?(v)` — folds to `Constant[true/false]` when every value is a
|
|
1371
|
+
# `Constant` (so equality is decidable) and the argument is a `Constant`.
|
|
1501
1372
|
def hash_has_value?(shape, _method_name, args)
|
|
1502
1373
|
return nil unless args.size == 1
|
|
1503
1374
|
return nil unless shape.closed?
|
|
@@ -1511,11 +1382,9 @@ module Rigor
|
|
|
1511
1382
|
Type::Combinator.constant_of(found)
|
|
1512
1383
|
end
|
|
1513
1384
|
|
|
1514
|
-
# `shape.default` / `default_proc` — a literal `HashShape`
|
|
1515
|
-
#
|
|
1516
|
-
# `
|
|
1517
|
-
# argument (still returns the default), `default_proc`
|
|
1518
|
-
# takes none — the `args.size <= 1` guard covers both.
|
|
1385
|
+
# `shape.default` / `default_proc` — a literal `HashShape` carries no default value or proc, so
|
|
1386
|
+
# both fold to `Constant[nil]`. `default` accepts an optional key argument (still returns the
|
|
1387
|
+
# default), `default_proc` takes none — the `args.size <= 1` guard covers both.
|
|
1519
1388
|
def hash_default(shape, _method_name, args)
|
|
1520
1389
|
return nil unless args.size <= 1
|
|
1521
1390
|
return nil unless shape.closed?
|
|
@@ -1523,10 +1392,9 @@ module Rigor
|
|
|
1523
1392
|
Type::Combinator.constant_of(nil)
|
|
1524
1393
|
end
|
|
1525
1394
|
|
|
1526
|
-
# `shape < other` / `<=` / `>` / `>=` — Hash containment
|
|
1527
|
-
#
|
|
1528
|
-
#
|
|
1529
|
-
# decidable). `<` / `>` are proper-subset / -superset.
|
|
1395
|
+
# `shape < other` / `<=` / `>` / `>=` — Hash containment comparison. Both sides must be closed
|
|
1396
|
+
# `HashShape`s whose values are all `Constant` (so pair equality is decidable). `<` / `>` are
|
|
1397
|
+
# proper-subset / -superset.
|
|
1530
1398
|
def hash_compare(shape, method_name, args)
|
|
1531
1399
|
return nil unless args.size == 1
|
|
1532
1400
|
return nil unless shape.closed? && shape.optional_keys.empty?
|
|
@@ -1542,9 +1410,8 @@ module Rigor
|
|
|
1542
1410
|
Type::Combinator.constant_of(hash_containment(method_name, left, right))
|
|
1543
1411
|
end
|
|
1544
1412
|
|
|
1545
|
-
# Unwraps a closed shape's pairs to a plain Ruby Hash of
|
|
1546
|
-
#
|
|
1547
|
-
# when any value is not a `Constant`.
|
|
1413
|
+
# Unwraps a closed shape's pairs to a plain Ruby Hash of `key => value` for value-equality
|
|
1414
|
+
# comparison. Returns nil when any value is not a `Constant`.
|
|
1548
1415
|
def constant_pairs(shape)
|
|
1549
1416
|
return nil unless shape.pairs.values.all?(Type::Constant)
|
|
1550
1417
|
|
|
@@ -1568,10 +1435,9 @@ module Rigor
|
|
|
1568
1435
|
left.size < right.size && hash_subset?(left, right)
|
|
1569
1436
|
end
|
|
1570
1437
|
|
|
1571
|
-
# `shape.has_key?(k)` / `key?(k)` / `member?(k)` /
|
|
1572
|
-
#
|
|
1573
|
-
#
|
|
1574
|
-
# shape is closed with no optional keys.
|
|
1438
|
+
# `shape.has_key?(k)` / `key?(k)` / `member?(k)` / `include?(k)` — folds to `Constant[true/false]`
|
|
1439
|
+
# when the argument is a `Constant[Symbol|String]` and the shape is closed with no optional
|
|
1440
|
+
# keys.
|
|
1575
1441
|
def hash_has_key?(shape, _method_name, args)
|
|
1576
1442
|
return nil unless args.size == 1
|
|
1577
1443
|
return nil unless shape.closed?
|
|
@@ -1587,10 +1453,9 @@ module Rigor
|
|
|
1587
1453
|
end
|
|
1588
1454
|
# rubocop:enable Style/ReturnNilInPredicateMethodDefinition
|
|
1589
1455
|
|
|
1590
|
-
# `shape.keys` — returns a `Tuple[Constant<k>…]` for a
|
|
1591
|
-
#
|
|
1592
|
-
#
|
|
1593
|
-
# so downstream `tuple[i]` projections stay precise.
|
|
1456
|
+
# `shape.keys` — returns a `Tuple[Constant<k>…]` for a closed shape with no optional keys; the
|
|
1457
|
+
# Tuple's arity matches the shape's per-key declaration order so downstream `tuple[i]`
|
|
1458
|
+
# projections stay precise.
|
|
1594
1459
|
def hash_keys(shape, _method_name, args)
|
|
1595
1460
|
return nil unless args.empty?
|
|
1596
1461
|
return nil unless shape.closed?
|
|
@@ -1599,9 +1464,8 @@ module Rigor
|
|
|
1599
1464
|
Type::Combinator.tuple_of(*shape.pairs.keys.map { |k| Type::Combinator.constant_of(k) })
|
|
1600
1465
|
end
|
|
1601
1466
|
|
|
1602
|
-
# `shape.values` — returns a `Tuple[V_1, …, V_n]` for a
|
|
1603
|
-
#
|
|
1604
|
-
# matching the shape's per-key value order.
|
|
1467
|
+
# `shape.values` — returns a `Tuple[V_1, …, V_n]` for a closed shape with no optional keys, the
|
|
1468
|
+
# Tuple's arity matching the shape's per-key value order.
|
|
1605
1469
|
def hash_values(shape, _method_name, args)
|
|
1606
1470
|
return nil unless args.empty?
|
|
1607
1471
|
return nil unless shape.closed?
|
|
@@ -1610,8 +1474,8 @@ module Rigor
|
|
|
1610
1474
|
Type::Combinator.tuple_of(*shape.pairs.values)
|
|
1611
1475
|
end
|
|
1612
1476
|
|
|
1613
|
-
# `shape.to_a` — returns a per-entry `Tuple[Tuple[K, V], …]`
|
|
1614
|
-
#
|
|
1477
|
+
# `shape.to_a` — returns a per-entry `Tuple[Tuple[K, V], …]` for a closed shape with no optional
|
|
1478
|
+
# keys.
|
|
1615
1479
|
def hash_to_a(shape, _method_name, args)
|
|
1616
1480
|
return nil unless args.empty?
|
|
1617
1481
|
return nil unless shape.closed?
|
|
@@ -1623,20 +1487,18 @@ module Rigor
|
|
|
1623
1487
|
Type::Combinator.tuple_of(*entries)
|
|
1624
1488
|
end
|
|
1625
1489
|
|
|
1626
|
-
# `shape.to_h` — Hash is structurally identical to its
|
|
1627
|
-
#
|
|
1490
|
+
# `shape.to_h` — Hash is structurally identical to its to_h (Ruby returns the receiver itself for
|
|
1491
|
+
# a Hash).
|
|
1628
1492
|
def hash_to_h(shape, _method_name, args)
|
|
1629
1493
|
return nil unless args.empty?
|
|
1630
1494
|
|
|
1631
1495
|
shape
|
|
1632
1496
|
end
|
|
1633
1497
|
|
|
1634
|
-
# `shape.invert` — swaps keys and values. Folds when
|
|
1635
|
-
#
|
|
1636
|
-
#
|
|
1637
|
-
#
|
|
1638
|
-
# alias under inversion, so Rigor declines on
|
|
1639
|
-
# collisions rather than silently dropping entries.
|
|
1498
|
+
# `shape.invert` — swaps keys and values. Folds when every value is a `Constant` whose value is a
|
|
1499
|
+
# Symbol or String (the only hashable types that `HashShape` accepts as keys). Duplicate values
|
|
1500
|
+
# would alias under inversion, so Rigor declines on collisions rather than silently dropping
|
|
1501
|
+
# entries.
|
|
1640
1502
|
def hash_invert(shape, _method_name, args)
|
|
1641
1503
|
return nil unless args.empty?
|
|
1642
1504
|
return nil unless shape.closed?
|
|
@@ -1652,10 +1514,9 @@ module Rigor
|
|
|
1652
1514
|
Type::Combinator.hash_shape_of(inverted)
|
|
1653
1515
|
end
|
|
1654
1516
|
|
|
1655
|
-
# `shape.first` — returns the first `[k, v]` pair as a
|
|
1656
|
-
#
|
|
1657
|
-
#
|
|
1658
|
-
# (open shapes might contribute extra keys at runtime).
|
|
1517
|
+
# `shape.first` — returns the first `[k, v]` pair as a 2-Tuple, or `Constant[nil]` when the
|
|
1518
|
+
# shape is empty. Folds only on closed shapes with no optional keys (open shapes might
|
|
1519
|
+
# contribute extra keys at runtime).
|
|
1659
1520
|
def hash_first(shape, _method_name, args)
|
|
1660
1521
|
return nil unless args.empty?
|
|
1661
1522
|
return nil unless shape.closed?
|
|
@@ -1666,9 +1527,8 @@ module Rigor
|
|
|
1666
1527
|
Type::Combinator.tuple_of(Type::Combinator.constant_of(key), value)
|
|
1667
1528
|
end
|
|
1668
1529
|
|
|
1669
|
-
# `shape.flatten` — flattens to `[k_1, v_1, k_2, v_2, …]`
|
|
1670
|
-
#
|
|
1671
|
-
# the per-key declaration order.
|
|
1530
|
+
# `shape.flatten` — flattens to `[k_1, v_1, k_2, v_2, …]` at depth 1. Closed shapes only; element
|
|
1531
|
+
# order matches the per-key declaration order.
|
|
1672
1532
|
def hash_flatten(shape, _method_name, args)
|
|
1673
1533
|
return nil unless args.empty?
|
|
1674
1534
|
return nil unless shape.closed?
|
|
@@ -1678,10 +1538,9 @@ module Rigor
|
|
|
1678
1538
|
Type::Combinator.tuple_of(*elements)
|
|
1679
1539
|
end
|
|
1680
1540
|
|
|
1681
|
-
# `shape.compact` — drops every entry whose value is
|
|
1682
|
-
# `Constant
|
|
1683
|
-
#
|
|
1684
|
-
# values decline so the RBS tier widens.
|
|
1541
|
+
# `shape.compact` — drops every entry whose value is `Constant[nil]`. Folds only when every
|
|
1542
|
+
# value is a `Constant` (so the drop set is decidable). Mixed-shape values decline so the RBS
|
|
1543
|
+
# tier widens.
|
|
1685
1544
|
def hash_compact(shape, _method_name, args)
|
|
1686
1545
|
return nil unless args.empty?
|
|
1687
1546
|
return nil unless shape.closed?
|
|
@@ -1692,11 +1551,9 @@ module Rigor
|
|
|
1692
1551
|
Type::Combinator.hash_shape_of(kept)
|
|
1693
1552
|
end
|
|
1694
1553
|
|
|
1695
|
-
# `shape.merge(other)` — when both sides are closed
|
|
1696
|
-
# HashShape
|
|
1697
|
-
#
|
|
1698
|
-
# entries on key collision (matching Ruby's runtime
|
|
1699
|
-
# `Hash#merge`).
|
|
1554
|
+
# `shape.merge(other)` — when both sides are closed HashShape with no optional keys, fold to the
|
|
1555
|
+
# merged HashShape. Right-hand entries override left-hand entries on key collision (matching
|
|
1556
|
+
# Ruby's runtime `Hash#merge`).
|
|
1700
1557
|
def hash_merge(shape, _method_name, args)
|
|
1701
1558
|
return nil unless args.size == 1
|
|
1702
1559
|
return nil unless shape.closed? && shape.optional_keys.empty?
|
|
@@ -1708,14 +1565,13 @@ module Rigor
|
|
|
1708
1565
|
Type::Combinator.hash_shape_of(shape.pairs.merge(other.pairs))
|
|
1709
1566
|
end
|
|
1710
1567
|
|
|
1711
|
-
# `shape[k]` and `shape.fetch(k)` for a static symbol/string
|
|
1712
|
-
#
|
|
1568
|
+
# `shape[k]` and `shape.fetch(k)` for a static symbol/string key. Missing-key resolution depends
|
|
1569
|
+
# on the method:
|
|
1713
1570
|
#
|
|
1714
|
-
# - `[]` returns `nil` at runtime; we surface `Constant[nil]`
|
|
1715
|
-
#
|
|
1716
|
-
# - `fetch` (no default, no block) raises `KeyError`; we let
|
|
1717
|
-
# the
|
|
1718
|
-
# not produce a value.
|
|
1571
|
+
# - `[]` returns `nil` at runtime; we surface `Constant[nil]` so the carrier is visible to
|
|
1572
|
+
# downstream narrowing.
|
|
1573
|
+
# - `fetch` (no default, no block) raises `KeyError`; we let the projection answer apply because
|
|
1574
|
+
# the runtime would not produce a value.
|
|
1719
1575
|
def hash_lookup(shape, method_name, args)
|
|
1720
1576
|
return nil unless args.size == 1
|
|
1721
1577
|
|
|
@@ -1729,12 +1585,10 @@ module Rigor
|
|
|
1729
1585
|
nil
|
|
1730
1586
|
end
|
|
1731
1587
|
|
|
1732
|
-
# `shape.dig(:a, :b, ...)` with a chain of static keys.
|
|
1733
|
-
#
|
|
1734
|
-
#
|
|
1735
|
-
#
|
|
1736
|
-
# to `Constant[nil]` (Ruby's `Hash#dig` short-circuits on
|
|
1737
|
-
# nil too).
|
|
1588
|
+
# `shape.dig(:a, :b, ...)` with a chain of static keys. Same recursion semantics as Tuple#`dig`:
|
|
1589
|
+
# each step looks up the key, then `chain_dig` continues with the resolved value as the new
|
|
1590
|
+
# receiver. Missing keys collapse to `Constant[nil]` (Ruby's `Hash#dig` short-circuits on nil
|
|
1591
|
+
# too).
|
|
1738
1592
|
def hash_dig(shape, _method_name, args)
|
|
1739
1593
|
return nil if args.empty?
|
|
1740
1594
|
|
|
@@ -1744,10 +1598,9 @@ module Rigor
|
|
|
1744
1598
|
chain_dig(step, args.drop(1))
|
|
1745
1599
|
end
|
|
1746
1600
|
|
|
1747
|
-
# Returns the per-step value type for a HashShape lookup
|
|
1748
|
-
#
|
|
1749
|
-
#
|
|
1750
|
-
# so the caller can fall through to the projection answer.
|
|
1601
|
+
# Returns the per-step value type for a HashShape lookup (or `Constant[nil]` for a known-missing
|
|
1602
|
+
# key). Returns `nil` when the argument is not a static symbol/string so the caller can fall
|
|
1603
|
+
# through to the projection answer.
|
|
1751
1604
|
def hash_dig_step(shape, arg)
|
|
1752
1605
|
return nil unless arg.is_a?(Type::Constant)
|
|
1753
1606
|
|
|
@@ -1776,11 +1629,9 @@ module Rigor
|
|
|
1776
1629
|
!shape.pairs.key?(arg.value)
|
|
1777
1630
|
end
|
|
1778
1631
|
|
|
1779
|
-
# `shape.values_at(:a, :b, ...)` with a list of static
|
|
1780
|
-
#
|
|
1781
|
-
#
|
|
1782
|
-
# keys, mirroring Ruby's runtime behaviour). Falls through
|
|
1783
|
-
# when any argument is not a static symbol/string.
|
|
1632
|
+
# `shape.values_at(:a, :b, ...)` with a list of static keys. Returns a `Tuple` whose per-position
|
|
1633
|
+
# values are the per-key value types (`Constant[nil]` for missing keys, mirroring Ruby's runtime
|
|
1634
|
+
# behaviour). Falls through when any argument is not a static symbol/string.
|
|
1784
1635
|
def hash_values_at(shape, _method_name, args)
|
|
1785
1636
|
return nil if args.empty?
|
|
1786
1637
|
|
|
@@ -1795,12 +1646,10 @@ module Rigor
|
|
|
1795
1646
|
Type::Combinator.tuple_of(*values)
|
|
1796
1647
|
end
|
|
1797
1648
|
|
|
1798
|
-
# `shape.slice(:a, :b, ...)` — returns a sub-HashShape
|
|
1799
|
-
#
|
|
1800
|
-
#
|
|
1801
|
-
#
|
|
1802
|
-
# semantics — no nil padding). Declines on open shapes
|
|
1803
|
-
# or when any argument is not a static key.
|
|
1649
|
+
# `shape.slice(:a, :b, ...)` — returns a sub-HashShape containing only the specified keys. All
|
|
1650
|
+
# arguments must be `Constant[Symbol|String]`. Keys not present in the shape are silently
|
|
1651
|
+
# omitted (matching Ruby's runtime semantics — no nil padding). Declines on open shapes or when
|
|
1652
|
+
# any argument is not a static key.
|
|
1804
1653
|
def hash_slice(shape, _method_name, args)
|
|
1805
1654
|
return nil if args.empty?
|
|
1806
1655
|
return nil unless shape.closed?
|
|
@@ -1819,11 +1668,9 @@ module Rigor
|
|
|
1819
1668
|
Type::Combinator.hash_shape_of(shape.pairs.slice(*requested))
|
|
1820
1669
|
end
|
|
1821
1670
|
|
|
1822
|
-
# `shape.except(:a, :b, ...)` — returns a sub-HashShape
|
|
1823
|
-
#
|
|
1824
|
-
#
|
|
1825
|
-
# are silently ignored. Declines on open shapes or when
|
|
1826
|
-
# any argument is not a static key.
|
|
1671
|
+
# `shape.except(:a, :b, ...)` — returns a sub-HashShape with the specified keys removed. All
|
|
1672
|
+
# arguments must be `Constant[Symbol|String]`. Keys not present in the shape are silently
|
|
1673
|
+
# ignored. Declines on open shapes or when any argument is not a static key.
|
|
1827
1674
|
def hash_except(shape, _method_name, args)
|
|
1828
1675
|
return nil if args.empty?
|
|
1829
1676
|
return nil unless shape.closed?
|
|
@@ -1843,11 +1690,9 @@ module Rigor
|
|
|
1843
1690
|
Type::Combinator.hash_shape_of(kept)
|
|
1844
1691
|
end
|
|
1845
1692
|
|
|
1846
|
-
# Continues a `dig` chain after the first step. Tuple and
|
|
1847
|
-
#
|
|
1848
|
-
#
|
|
1849
|
-
# Array#dig do the same at runtime); anything else falls
|
|
1850
|
-
# through so the projection answer applies.
|
|
1693
|
+
# Continues a `dig` chain after the first step. Tuple and HashShape members re-dispatch into the
|
|
1694
|
+
# catalogue; `Constant[nil]` short-circuits the chain (Hash#dig and Array#dig do the same at
|
|
1695
|
+
# runtime); anything else falls through so the projection answer applies.
|
|
1851
1696
|
def chain_dig(receiver, args)
|
|
1852
1697
|
return receiver if args.empty?
|
|
1853
1698
|
|