rigortype 0.3.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/manual/02-cli-reference.md +63 -2
- data/docs/manual/03-configuration.md +7 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +520 -49
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_session.rb +36 -4
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +20 -11
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
- data/lib/rigor/analysis/runner.rb +278 -20
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +53 -10
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/incremental_snapshot.rb +33 -1
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +0 -1
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/configuration/severity_profile.rb +19 -3
- data/lib/rigor/configuration.rb +66 -3
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +43 -31
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/lockfile_resolver.rb +17 -0
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_loader.rb +399 -133
- data/lib/rigor/environment.rb +103 -38
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +73 -27
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +387 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1532 -496
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +167 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/multi_target_binder.rb +340 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +540 -120
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +4615 -551
- data/lib/rigor/inference/statement_evaluator.rb +3176 -490
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +168 -5
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +3 -1
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +1 -2
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +19 -6
- data/lib/rigor/reflection.rb +72 -105
- data/lib/rigor/scope/discovery_index.rb +135 -2
- data/lib/rigor/scope.rb +859 -49
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +558 -31
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +88 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +12 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +1 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +135 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +9 -7
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +33 -8
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +108 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
require_relative "../mutation_widening"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module MethodDispatcher
|
|
9
|
+
# Issue #1092 — the substitute {RbsDispatch} hands the translator for `Bases::Self` on an instance
|
|
10
|
+
# receiver whose projection carries type arguments, so `Array[Integer]#tap {}` answers
|
|
11
|
+
# `Array[Integer]` rather than the raw `Array` the class name alone builds. `nil` keeps that raw
|
|
12
|
+
# nominal.
|
|
13
|
+
#
|
|
14
|
+
# The substitute is always a nominal built from the receiver's projected arguments, never the
|
|
15
|
+
# receiver's shape: the pure self-returners keep the shape through ShapeDispatch (ADR-76 WD3), and a
|
|
16
|
+
# block can mutate the receiver through its yielded alias, which MutationWidening does not follow.
|
|
17
|
+
# For the same reason every argument is widened DEEPLY — a literal to its class, a nested
|
|
18
|
+
# `Tuple` / `HashShape` to its projected nominal — so `[[1, 2]].tap { |a| a[0] << 3 }` cannot fold
|
|
19
|
+
# `.first.size == 3`.
|
|
20
|
+
#
|
|
21
|
+
# A `-> self` method that can change the element types keeps the arguments only when the call
|
|
22
|
+
# provably adds nothing outside them; otherwise the call value would claim `ints.map!(&:to_s)` is
|
|
23
|
+
# still an `Array[Integer]`.
|
|
24
|
+
module SelfSubstitute
|
|
25
|
+
# In-place methods that cannot change the receiver's element, key, or value types.
|
|
26
|
+
TYPE_PRESERVING = %i[
|
|
27
|
+
sort! sort_by! reverse! uniq! compact! shuffle! rotate!
|
|
28
|
+
select! filter! reject! keep_if delete_if delete subtract
|
|
29
|
+
clear compare_by_identity rehash reset
|
|
30
|
+
].to_set.freeze
|
|
31
|
+
|
|
32
|
+
# Adders whose positional arguments are each a new element (`Array` / `Set`).
|
|
33
|
+
ELEMENT_ADDERS = %i[<< push append unshift prepend add add?].to_set.freeze
|
|
34
|
+
|
|
35
|
+
# Adders whose positional arguments are each a collection of the receiver's own class.
|
|
36
|
+
COLLECTION_ADDERS = %i[concat merge merge! update].to_set.freeze
|
|
37
|
+
|
|
38
|
+
# Every other name MutationWidening knows as a receiver mutator (`map!`, `replace`, `fill`,
|
|
39
|
+
# `flatten!`, `transform_values!`, ...) rewrites or may rewrite the element types.
|
|
40
|
+
KNOWN_MUTATORS = (MutationWidening::ARRAY_MUTATORS | MutationWidening::HASH_MUTATORS).freeze
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
# @param args — the call's positional argument types.
|
|
45
|
+
# @param block_type — the call's block return type, nil without a block.
|
|
46
|
+
def for(receiver, receiver_args, method_name, args, block_type)
|
|
47
|
+
return nil if receiver_args.empty?
|
|
48
|
+
|
|
49
|
+
case receiver
|
|
50
|
+
when Type::Nominal
|
|
51
|
+
nominal_self(receiver.class_name, receiver_args, method_name, args, block_type)
|
|
52
|
+
when Type::Tuple
|
|
53
|
+
projected_self("Array", receiver_args) unless MutationWidening::ARRAY_MUTATORS.include?(method_name)
|
|
54
|
+
when Type::HashShape
|
|
55
|
+
projected_self("Hash", receiver_args) unless MutationWidening::HASH_MUTATORS.include?(method_name)
|
|
56
|
+
when Type::Refined, Type::Difference
|
|
57
|
+
self.for(receiver.base, receiver_args, method_name, args, block_type)
|
|
58
|
+
when Type::Dynamic
|
|
59
|
+
inner = self.for(receiver.static_facet, receiver_args, method_name, args, block_type)
|
|
60
|
+
inner && Type::Combinator.dynamic(inner)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def nominal_self(class_name, receiver_args, method_name, args, block_type)
|
|
65
|
+
return nil unless preserves_type_args?(class_name, receiver_args, method_name, args, block_type)
|
|
66
|
+
|
|
67
|
+
projected_self(class_name, receiver_args)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Nil when every argument widens to the bare `Dynamic[top]`: `Hash[untyped, untyped]` says nothing
|
|
71
|
+
# the raw `Hash` does not.
|
|
72
|
+
def projected_self(class_name, receiver_args)
|
|
73
|
+
type_args = receiver_args.map { |arg| deep_widen(arg) }
|
|
74
|
+
return nil if type_args.all? { |arg| untyped?(arg) }
|
|
75
|
+
|
|
76
|
+
Type::Combinator.nominal_of(class_name, type_args: type_args)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def preserves_type_args?(class_name, receiver_args, method_name, args, block_type)
|
|
80
|
+
return true if TYPE_PRESERVING.include?(method_name)
|
|
81
|
+
|
|
82
|
+
if ELEMENT_ADDERS.include?(method_name)
|
|
83
|
+
receiver_args.size == 1 && all_provably_within?(receiver_args.first, args)
|
|
84
|
+
elsif method_name == :insert
|
|
85
|
+
receiver_args.size == 1 && all_provably_within?(receiver_args.first, args.drop(1))
|
|
86
|
+
elsif COLLECTION_ADDERS.include?(method_name)
|
|
87
|
+
block_type.nil? &&
|
|
88
|
+
all_provably_within?(Type::Combinator.nominal_of(class_name, type_args: receiver_args), args)
|
|
89
|
+
else
|
|
90
|
+
!KNOWN_MUTATORS.include?(method_name) && !method_name.end_with?("!")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def all_provably_within?(bound, args)
|
|
95
|
+
args.all? { |arg| !imprecise?(arg) && bound.accepts(arg, mode: :gradual).yes? }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def imprecise?(type)
|
|
99
|
+
case type
|
|
100
|
+
when Type::Dynamic then true
|
|
101
|
+
when Type::Union then type.members.any? { |member| imprecise?(member) }
|
|
102
|
+
else false
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def untyped?(type)
|
|
107
|
+
type.is_a?(Type::Dynamic) && type.static_facet.is_a?(Type::Top)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# `widen_value_pinned`, extended through nested shape carriers and applied type arguments.
|
|
111
|
+
def deep_widen(type)
|
|
112
|
+
case type
|
|
113
|
+
when Type::Tuple
|
|
114
|
+
return Type::Combinator.nominal_of("Array") if type.elements.empty?
|
|
115
|
+
|
|
116
|
+
Type::Combinator.nominal_of("Array", type_args: [deep_widen(Type::Combinator.union(*type.elements))])
|
|
117
|
+
when Type::HashShape
|
|
118
|
+
deep_widen_hash_shape(type)
|
|
119
|
+
when Type::Nominal
|
|
120
|
+
return type if type.type_args.empty?
|
|
121
|
+
|
|
122
|
+
Type::Combinator.nominal_of(type.class_name, type_args: type.type_args.map { |arg| deep_widen(arg) })
|
|
123
|
+
when Type::Union
|
|
124
|
+
Type::Combinator.union(*type.members.map { |member| deep_widen(member) })
|
|
125
|
+
when Type::Dynamic
|
|
126
|
+
untyped?(type) ? type : Type::Combinator.dynamic(deep_widen(type.static_facet))
|
|
127
|
+
else
|
|
128
|
+
Type::Combinator.widen_value_pinned(type)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def deep_widen_hash_shape(shape)
|
|
133
|
+
return Type::Combinator.nominal_of("Hash") if shape.pairs.empty?
|
|
134
|
+
|
|
135
|
+
keys = Type::Combinator.union(*shape.pairs.keys.map { |key| Type::Combinator.constant_of(key) })
|
|
136
|
+
values = Type::Combinator.union(*shape.pairs.values)
|
|
137
|
+
Type::Combinator.nominal_of("Hash", type_args: [deep_widen(keys), deep_widen(values)])
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -197,21 +197,11 @@ module Rigor
|
|
|
197
197
|
Type::Difference => :dispatch_difference,
|
|
198
198
|
Type::Refined => :dispatch_refined,
|
|
199
199
|
Type::Intersection => :dispatch_intersection,
|
|
200
|
-
Type::IntegerRange => :dispatch_integer_range
|
|
200
|
+
Type::IntegerRange => :dispatch_integer_range,
|
|
201
|
+
Type::FloatRange => :dispatch_float_range
|
|
201
202
|
}.freeze
|
|
202
203
|
private_constant :RECEIVER_HANDLERS
|
|
203
204
|
|
|
204
|
-
# v0.1.1 Track 1 slice 5b — `Integer#to_s(base)` on a non-negative `IntegerRange` receiver. The
|
|
205
|
-
# output of `n.to_s(b)` for `n >= 0` is digit-string-only (no leading sign), so when the base is in
|
|
206
|
-
# this table the result lifts to the matching imported refinement. Bases not listed (2, 36, ...)
|
|
207
|
-
# keep the v0.1.0 baseline since Rigor has no carrier for the resulting alphabet.
|
|
208
|
-
TO_S_BASE_REFINEMENTS = {
|
|
209
|
-
10 => :decimal_int_string,
|
|
210
|
-
8 => :octal_int_string,
|
|
211
|
-
16 => :hex_int_string
|
|
212
|
-
}.freeze
|
|
213
|
-
private_constant :TO_S_BASE_REFINEMENTS
|
|
214
|
-
|
|
215
205
|
def try_dispatch(context)
|
|
216
206
|
receiver = context.receiver
|
|
217
207
|
method_name = context.method_name
|
|
@@ -304,7 +294,9 @@ module Rigor
|
|
|
304
294
|
when "String"
|
|
305
295
|
dispatch_string_binary_from_arg(method_name, args.first) if args.size == 1
|
|
306
296
|
when "Integer"
|
|
307
|
-
|
|
297
|
+
integer_nominal_projection(method_name, args)
|
|
298
|
+
when "Float"
|
|
299
|
+
float_to_s_projection(method_name, args)
|
|
308
300
|
when "Array"
|
|
309
301
|
case method_name
|
|
310
302
|
when :flatten then array_nominal_flatten(nominal, args)
|
|
@@ -313,6 +305,65 @@ module Rigor
|
|
|
313
305
|
end
|
|
314
306
|
end
|
|
315
307
|
|
|
308
|
+
# `Integer#to_s` / `Integer#inspect` (#993) on a bare `Nominal[Integer]` receiver, ahead of the
|
|
309
|
+
# existing `:*` binary fold. `#inspect` takes no argument and matches `#to_s()` byte for byte, so
|
|
310
|
+
# it routes through the same no-arg branch.
|
|
311
|
+
def integer_nominal_projection(method_name, args)
|
|
312
|
+
case method_name
|
|
313
|
+
when :to_s then integer_to_s_projection(args)
|
|
314
|
+
when :inspect then args.empty? ? Type::Combinator.decimal_int_string : nil
|
|
315
|
+
else dispatch_integer_binary_from_arg(method_name, args.first) if args.size == 1
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# `decimal-int-string`'s predicate (`/\A-?\d+\z/`) admits the optional sign, so — unlike the
|
|
320
|
+
# `IntegerRange#to_s` tier above, which declines a signed range because it cannot yet prove the
|
|
321
|
+
# sign is absent — a bare `Integer` (sign completely unknown) still reaches it for the base-10
|
|
322
|
+
# case: base 10 is Ruby's default sign-and-digits grammar for every Integer. Any other base,
|
|
323
|
+
# known or not statically known, drops to `non-empty-string`: `to_s(base)` never returns `""`,
|
|
324
|
+
# but a non-decimal base's digits (`255.to_s(16)` is `"ff"`, not the `0xff` literal) match no
|
|
325
|
+
# registered refinement.
|
|
326
|
+
def integer_to_s_projection(args)
|
|
327
|
+
return Type::Combinator.decimal_int_string if args.empty?
|
|
328
|
+
return nil unless args.size == 1
|
|
329
|
+
|
|
330
|
+
decimal_base?(args.first) ? Type::Combinator.decimal_int_string : Type::Combinator.non_empty_string
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def decimal_base?(arg)
|
|
334
|
+
arg.is_a?(Type::Constant) && arg.value == 10
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# `Float#to_s` / `Float#inspect` (#993) on a bare `Nominal[Float]` receiver. No finiteness proof
|
|
338
|
+
# is available here — a bare `Float` admits `Infinity` / `-Infinity` / `NaN`, none of which are
|
|
339
|
+
# Ruby numeric literals — so the floor is `non-empty-string` (Ruby's conversion is total and
|
|
340
|
+
# never returns `""`). A `FloatRange` receiver with proven-finite bounds reaches `numeric-string`
|
|
341
|
+
# instead; see {#dispatch_float_range}.
|
|
342
|
+
def float_to_s_projection(method_name, args)
|
|
343
|
+
return nil unless %i[to_s inspect].include?(method_name)
|
|
344
|
+
return nil unless args.empty?
|
|
345
|
+
|
|
346
|
+
Type::Combinator.non_empty_string
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# `Float#to_s` / `Float#inspect` (#993) on a `FloatRange` receiver. `min` / `canonical_max` both
|
|
350
|
+
# finite (Ruby's own `Float#finite?`, the same check {MethodDispatcher::ConstantFolding}'s
|
|
351
|
+
# `float_range_finite` fold uses) is exactly a finiteness proof: no bounded range contains `NaN`,
|
|
352
|
+
# and finite bounds rule out both infinities too. `finite-float` satisfies it, as does any
|
|
353
|
+
# narrower bounded sub-range and the `f.finite?` truthy-edge narrowing (ADR-109 WD5) that
|
|
354
|
+
# produces `finite-float` — but `Float[0.0..]` does NOT, because its endless upper bound is
|
|
355
|
+
# `Float::INFINITY`.
|
|
356
|
+
def dispatch_float_range(range, method_name, args)
|
|
357
|
+
return nil unless args.empty?
|
|
358
|
+
return nil unless %i[to_s inspect].include?(method_name)
|
|
359
|
+
|
|
360
|
+
if range.min.finite? && range.canonical_max.finite?
|
|
361
|
+
Type::Combinator.numeric_string
|
|
362
|
+
else
|
|
363
|
+
Type::Combinator.non_empty_string
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
316
367
|
# `Array[T]#compact` — `compact` removes every `nil` element, so the result element type is `T`
|
|
317
368
|
# with its `nil` constituent stripped (`Array[Node?]#compact` → `Array[Node]`). Mirrors the
|
|
318
369
|
# `Tuple#compact` constant fold for the generic element case. Declines when the receiver carries
|
|
@@ -418,10 +469,18 @@ module Rigor
|
|
|
418
469
|
|
|
419
470
|
# `IntegerRange#to_s` precision (v0.1.1 Track 1 slice 5b). When the range's lower bound is
|
|
420
471
|
# `>= 0`, every member is a non-negative integer and `to_s(base)` returns a digit-string with no
|
|
421
|
-
# leading sign.
|
|
422
|
-
#
|
|
423
|
-
#
|
|
424
|
-
#
|
|
472
|
+
# leading sign. Base 10 is Ruby's decimal-literal grammar, so the result lifts to
|
|
473
|
+
# `decimal-int-string`. Signed ranges fall through entirely (the result could carry a `-` sign
|
|
474
|
+
# that `decimal-int-string` alone would need a sign proof for, matching the
|
|
475
|
+
# `Nominal[Integer]` tier's own signed-base-10 carve-out below).
|
|
476
|
+
#
|
|
477
|
+
# #1004 — every other base (a different literal, such as 8 or 16, or a value that is not
|
|
478
|
+
# statically known) does NOT lift to `octal-int-string` / `hex-int-string`: those refinements'
|
|
479
|
+
# predicates REQUIRE the `0o` / `0x` prefix Ruby's own `to_s(base)` never emits
|
|
480
|
+
# (`64.to_s(8)` is `"100"`, `255.to_s(16)` is `"ff"` — neither has a prefix), so a producer
|
|
481
|
+
# that claimed them was unsound. The floor is `non-empty-string`, same as the bare
|
|
482
|
+
# `Nominal[Integer]` path (#993 / #1001) reaches for the identical reason: `to_s(base)` is
|
|
483
|
+
# total and never returns `""`.
|
|
425
484
|
def dispatch_integer_range(range, method_name, args)
|
|
426
485
|
return nil unless method_name == :to_s
|
|
427
486
|
return nil unless range.lower >= 0
|
|
@@ -429,10 +488,7 @@ module Rigor
|
|
|
429
488
|
base = base_argument(args)
|
|
430
489
|
return nil if base.nil?
|
|
431
490
|
|
|
432
|
-
|
|
433
|
-
return nil if refinement.nil?
|
|
434
|
-
|
|
435
|
-
Type::Combinator.public_send(refinement)
|
|
491
|
+
base == 10 ? Type::Combinator.decimal_int_string : Type::Combinator.non_empty_string
|
|
436
492
|
end
|
|
437
493
|
|
|
438
494
|
# `to_s` with no argument defaults to base 10. With one argument, the value MUST be a
|
|
@@ -1556,14 +1612,18 @@ module Rigor
|
|
|
1556
1612
|
Type::Combinator.constant_of(found)
|
|
1557
1613
|
end
|
|
1558
1614
|
|
|
1559
|
-
# `shape.default` / `default_proc` — a literal `HashShape` carries no default value or proc,
|
|
1560
|
-
# both fold to `Constant[nil]`. `default` accepts an optional key argument (still returns the
|
|
1615
|
+
# `shape.default` / `default_proc` — a closed literal `HashShape` carries no default value or proc,
|
|
1616
|
+
# so both fold to `Constant[nil]`. `default` accepts an optional key argument (still returns the
|
|
1561
1617
|
# default), `default_proc` takes none — the `args.size <= 1` guard covers both.
|
|
1562
|
-
|
|
1618
|
+
#
|
|
1619
|
+
# An open shape's `default` is `untyped`. The default is what an unseen key reads, which the known
|
|
1620
|
+
# values do not bound — the RBS answer `V?` would bound it by them, so `h = { a: 1 }; h.default = 0`
|
|
1621
|
+
# (which opens the shape, see `HashLookupMutation`) read `h.default` as `1?`. `default_proc` defers.
|
|
1622
|
+
def hash_default(shape, method_name, args)
|
|
1563
1623
|
return nil unless args.size <= 1
|
|
1564
|
-
return nil
|
|
1624
|
+
return Type::Combinator.constant_of(nil) if shape.closed?
|
|
1565
1625
|
|
|
1566
|
-
Type::Combinator.
|
|
1626
|
+
Type::Combinator.untyped if method_name == :default
|
|
1567
1627
|
end
|
|
1568
1628
|
|
|
1569
1629
|
# `shape < other` / `<=` / `>` / `>=` — Hash containment comparison. Both sides must be closed
|
|
@@ -1772,17 +1832,23 @@ module Rigor
|
|
|
1772
1832
|
#
|
|
1773
1833
|
# On an `:open` shape neither applies — the key is unknown rather than missing, so both methods
|
|
1774
1834
|
# answer the `untyped` step from {#hash_dig_step}.
|
|
1835
|
+
#
|
|
1836
|
+
# `[]` takes whatever step {#hash_dig_step} gives a computed key. `fetch` answers only literal keys
|
|
1837
|
+
# none of which can miss — a required declared key, or an undeclared one on an open shape — and
|
|
1838
|
+
# defers otherwise: a miss raises instead of reading nil, so the projection's nil-free value union
|
|
1839
|
+
# is already the sound answer.
|
|
1775
1840
|
def hash_lookup(shape, method_name, args)
|
|
1776
1841
|
return nil unless args.size == 1
|
|
1777
1842
|
|
|
1778
1843
|
step = hash_dig_step(shape, args.first)
|
|
1779
1844
|
return nil if step.nil?
|
|
1780
|
-
return nil if method_name == :fetch && optional_key_step?(shape, args.first)
|
|
1781
|
-
return step unless missing_key_step?(shape, args.first)
|
|
1782
|
-
|
|
1783
1845
|
return step if method_name == :[]
|
|
1784
1846
|
|
|
1785
|
-
|
|
1847
|
+
keys = literal_keys(args.first)
|
|
1848
|
+
return nil if keys.nil?
|
|
1849
|
+
return nil if keys.any? { |key| shape.optional_key?(key) || (shape.closed? && !shape.pairs.key?(key)) }
|
|
1850
|
+
|
|
1851
|
+
step
|
|
1786
1852
|
end
|
|
1787
1853
|
|
|
1788
1854
|
# `shape.dig(:a, :b, ...)` with a chain of static keys. Same recursion semantics as Tuple#`dig`:
|
|
@@ -1799,20 +1865,45 @@ module Rigor
|
|
|
1799
1865
|
end
|
|
1800
1866
|
|
|
1801
1867
|
# Returns the per-step value type for a HashShape lookup (or `Constant[nil]` for a known-missing
|
|
1802
|
-
# key)
|
|
1803
|
-
# through to the projection answer.
|
|
1868
|
+
# key), or `nil` so the caller falls through to the projection answer.
|
|
1804
1869
|
#
|
|
1805
1870
|
# A key outside `pairs` only resolves to `Constant[nil]` on a CLOSED shape, where `pairs` is the
|
|
1806
1871
|
# whole key set and the runtime read provably returns nil. On an `:open` shape the key set is by
|
|
1807
1872
|
# definition not exhausted, so an undeclared key is *unknown*, not absent — it may hold a value
|
|
1808
1873
|
# of any type. Typing it `Constant[nil]` there would put `call.undefined-method` on the next line
|
|
1809
1874
|
# of correct code (`payload[:undeclared].upcase`), so the open case answers `untyped`.
|
|
1875
|
+
#
|
|
1876
|
+
# A union of value-pinned keys that the shape all declares answers the union of their steps, which
|
|
1877
|
+
# is exact. Any other key is computed — including a union with an undeclared member, whose per-member
|
|
1878
|
+
# answer on a stale shape is the same lone `Constant[nil]` the empty-shape rule below refuses. On a
|
|
1879
|
+
# closed shape a computed key may match any declared key or none, so the step is every value plus
|
|
1880
|
+
# `Constant[nil]`. Deferring it instead let the projection answer the value union alone — nil-free,
|
|
1881
|
+
# since `RbsDispatch` reads past `Hash#[]`'s `%a{implicitly-returns-nil}` — and
|
|
1882
|
+
# `o = { a: 1 }; o[name] == 1` folded always-truthy. An open shape still defers: its undeclared keys
|
|
1883
|
+
# may hold any value. So does the empty shape: a hash read by a computed key while it looks empty is
|
|
1884
|
+
# one filled where the analysis did not see — a loop body's earlier iteration
|
|
1885
|
+
# (`h = {}; xs.each { |x| h[x] = h[x] ? h[x] + 1 : 1 }`), a cache (`(@cache ||= {})[key]`), an
|
|
1886
|
+
# `instance_eval`ed file — and answering `Constant[nil]` there folds its own guard always-falsey.
|
|
1810
1887
|
def hash_dig_step(shape, arg)
|
|
1811
|
-
|
|
1888
|
+
keys = literal_keys(arg)
|
|
1889
|
+
if keys && (keys.size == 1 || keys.all? { |key| shape.pairs.key?(key) })
|
|
1890
|
+
return Type::Combinator.union(*keys.map { |key| static_key_step(shape, key) })
|
|
1891
|
+
end
|
|
1892
|
+
return nil if shape.open? || shape.pairs.empty?
|
|
1812
1893
|
|
|
1813
|
-
|
|
1814
|
-
|
|
1894
|
+
Type::Combinator.union(*shape.pairs.values, Type::Combinator.constant_of(nil))
|
|
1895
|
+
end
|
|
1896
|
+
|
|
1897
|
+
# The value-pinned keys `arg` can take — one for a scalar `Constant`, one per member for a union of
|
|
1898
|
+
# them — or `nil` when it can take any other value.
|
|
1899
|
+
def literal_keys(arg)
|
|
1900
|
+
members = arg.is_a?(Type::Union) ? arg.members : [arg]
|
|
1901
|
+
return nil unless members.all? { |member| member.is_a?(Type::Constant) && static_shape_key?(member.value) }
|
|
1815
1902
|
|
|
1903
|
+
members.map(&:value)
|
|
1904
|
+
end
|
|
1905
|
+
|
|
1906
|
+
def static_key_step(shape, key)
|
|
1816
1907
|
if shape.pairs.key?(key)
|
|
1817
1908
|
value = shape.pairs[key]
|
|
1818
1909
|
return value unless shape.optional_key?(key)
|
|
@@ -1825,26 +1916,10 @@ module Rigor
|
|
|
1825
1916
|
Type::Combinator.constant_of(nil)
|
|
1826
1917
|
end
|
|
1827
1918
|
|
|
1828
|
-
def optional_key_step?(shape, arg)
|
|
1829
|
-
return false unless arg.is_a?(Type::Constant)
|
|
1830
|
-
|
|
1831
|
-
shape.optional_key?(arg.value)
|
|
1832
|
-
end
|
|
1833
|
-
|
|
1834
|
-
# Whether the key is provably ABSENT — which only a closed shape can establish. An open shape
|
|
1835
|
-
# permits keys outside `pairs`, so a key it does not declare is unknown rather than missing, and
|
|
1836
|
-
# `fetch` must not be deferred on the theory that Ruby would raise `KeyError`: the key may well
|
|
1837
|
-
# be there. {#hash_dig_step} has already answered `untyped` for that case.
|
|
1838
|
-
def missing_key_step?(shape, arg)
|
|
1839
|
-
return false unless arg.is_a?(Type::Constant)
|
|
1840
|
-
return false if shape.open?
|
|
1841
|
-
|
|
1842
|
-
!shape.pairs.key?(arg.value)
|
|
1843
|
-
end
|
|
1844
|
-
|
|
1845
1919
|
# `shape.values_at(:a, :b, ...)` with a list of static keys. Returns a `Tuple` whose per-position
|
|
1846
1920
|
# values are the per-key value types (`Constant[nil]` for missing keys, mirroring Ruby's runtime
|
|
1847
|
-
# behaviour).
|
|
1921
|
+
# behaviour). A computed key takes the nil-bearing step {#hash_dig_step} gives it; the call falls
|
|
1922
|
+
# through when any step does (a computed key on an open shape).
|
|
1848
1923
|
def hash_values_at(shape, _method_name, args)
|
|
1849
1924
|
return nil if args.empty?
|
|
1850
1925
|
|
|
@@ -364,7 +364,7 @@ module Rigor
|
|
|
364
364
|
members: widen_unowned_emptiness(current.members.merge(member => assigned_type)),
|
|
365
365
|
class_name: current.class_name
|
|
366
366
|
)
|
|
367
|
-
scope.
|
|
367
|
+
scope.with_mutated_local(receiver.name, rebound)
|
|
368
368
|
end
|
|
369
369
|
end
|
|
370
370
|
end
|