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
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require_relative "../../type"
|
|
4
4
|
require_relative "../acceptance"
|
|
5
5
|
require_relative "../rbs_type_translator"
|
|
6
|
+
require_relative "alias_strict_nominals"
|
|
7
|
+
require_relative "facet_distribution"
|
|
8
|
+
require_relative "proven_overload"
|
|
6
9
|
require_relative "receiver_affinity"
|
|
7
10
|
|
|
8
11
|
module Rigor
|
|
@@ -14,6 +17,9 @@ module Rigor
|
|
|
14
17
|
# 1. Filter overloads by positional arity (required, optional and rest_positionals are honored;
|
|
15
18
|
# required_keywords disqualify the overload because we do not yet thread keyword args through
|
|
16
19
|
# `call_arg_types`).
|
|
20
|
+
# 1a. **Pass 0 — proven, in declared order (#1344).** With only plain-value arguments, the first
|
|
21
|
+
# overload they do not rule out wins outright when each of its params names its argument's own
|
|
22
|
+
# class and accepts it with a `yes`; every later pass reads the receiver-affinity order instead.
|
|
17
23
|
# 2. **Pass 1 — strict matches first.** Among the arity-matching overloads, prefer the first one
|
|
18
24
|
# whose every (param, arg) pair returns a `yes` or `maybe` answer AND whose param types do NOT
|
|
19
25
|
# translate through `RBS::Types::Alias` / `Interface` / `Intersection`. The translator demotes
|
|
@@ -38,31 +44,7 @@ module Rigor
|
|
|
38
44
|
module OverloadSelector
|
|
39
45
|
module_function
|
|
40
46
|
|
|
41
|
-
#
|
|
42
|
-
# Matching an overload against an Integer literal should pick the `(int) -> Array[Elem]` body over
|
|
43
|
-
# the `(string) -> String` body because Integer satisfies `int`'s strict arm and not `string`'s.
|
|
44
|
-
# The translator collapses both aliases to `Dynamic[Top]` (interfaces are not structurally matched
|
|
45
|
-
# yet), so a dedicated pass 1.5 between strict and gradual consults this map to pick the alias
|
|
46
|
-
# whose strict arm matches.
|
|
47
|
-
#
|
|
48
|
-
# Symbol keys are the alias names as they appear under `RBS::Types::Alias#name.to_s` (the `name` is
|
|
49
|
-
# a `TypeName` whose `to_s` includes the `::` prefix). Values are an Array of class names whose
|
|
50
|
-
# Nominal[..] form is the alias's strict-arm matcher.
|
|
51
|
-
ALIAS_STRICT_NOMINALS = {
|
|
52
|
-
"::int" => ["Integer"],
|
|
53
|
-
"::string" => ["String"],
|
|
54
|
-
"::interned" => %w[Symbol String],
|
|
55
|
-
"::io" => ["IO"],
|
|
56
|
-
"::encoding" => %w[Encoding String],
|
|
57
|
-
"::path" => ["String"],
|
|
58
|
-
"::boolean" => %w[TrueClass FalseClass],
|
|
59
|
-
# `range[T] = Range[T] | _Range[T]`. Generic, unlike the others, but the strict arm is still a
|
|
60
|
-
# single nominal and the args are irrelevant to this pass. rbs 4.1 rewrote `Array#[]`'s slicing
|
|
61
|
-
# overload from `(::Range[::Integer?])` to `(range[int])`; without the entry both it and the
|
|
62
|
-
# `(int) -> E` overload look alias-typed, so `a[1..2]` resolved to the element type.
|
|
63
|
-
"::range" => ["Range"]
|
|
64
|
-
}.freeze
|
|
65
|
-
private_constant :ALIAS_STRICT_NOMINALS
|
|
47
|
+
# `ALIAS_STRICT_NOMINALS`, the alias-strict pass's table, lives in `alias_strict_nominals.rb`.
|
|
66
48
|
|
|
67
49
|
# @param arg_types — caller-provided types in positional order. Empty when
|
|
68
50
|
# there are no arguments.
|
|
@@ -79,22 +61,40 @@ module Rigor
|
|
|
79
61
|
# declaration.
|
|
80
62
|
# @return the chosen overload, or nil when the definition has no method
|
|
81
63
|
# types at all.
|
|
82
|
-
def select(method_definition, **) = select_candidates(method_definition, **).first
|
|
64
|
+
def select(method_definition, **) = select_candidates(method_definition, member_wise: false, **).first
|
|
83
65
|
|
|
84
|
-
# Issue #521 — like {.select}, but when
|
|
85
|
-
#
|
|
86
|
-
#
|
|
66
|
+
# Issue #521 — like {.select}, but when an imprecise argument (`Dynamic[Top]`, or since #1021 a
|
|
67
|
+
# union with a `Dynamic[Top]` member) reaches the gradual pass it returns EVERY gradually-matching
|
|
68
|
+
# overload instead of pinning the first. An untyped argument accepts every param
|
|
69
|
+
# indiscriminately, so "first gradual match" is decided by overload-list
|
|
87
70
|
# position, not by types — `[true] * n` with an untyped `n` pinned `Array#*(string) -> String`
|
|
88
71
|
# and answered a wrong precise type the runtime can contradict. The caller unions the candidates'
|
|
89
|
-
# returns, which contains the truth whichever overload the runtime takes.
|
|
90
|
-
#
|
|
91
|
-
#
|
|
72
|
+
# returns, which contains the truth whichever overload the runtime takes. The member-wise reading of a
|
|
73
|
+
# two-member `Dynamic` facet (#1350, `FacetDistribution`) returns one candidate per member the same way.
|
|
74
|
+
# Every other path (strict, alias-resolved, typed-gradual, arity fallback) yields exactly one candidate,
|
|
75
|
+
# and `select` (`member_wise: false`) keeps its historical single answer.
|
|
92
76
|
#
|
|
93
77
|
# @return matching overloads; empty when the definition declares none.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
78
|
+
# rubocop:disable-next Metrics/ParameterLists -- the selection keywords plus the member-wise switch.
|
|
79
|
+
def select_candidates(definition, arg_types:, self_type:, instance_type:, type_vars: {},
|
|
80
|
+
block_required: false, environment: nil, member_wise: true)
|
|
81
|
+
unless FacetDistribution.faceted?(arg_types)
|
|
82
|
+
return select_declared(definition, arg_types, self_type, instance_type, type_vars, block_required,
|
|
83
|
+
environment, false)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
FacetDistribution.select(arg_types, definition, member_wise:, environment:) do |args, member|
|
|
87
|
+
select_declared(definition, args, self_type, instance_type, type_vars, block_required, environment, member)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The selection proper, positional so the hot path forwards no keyword hash. A member-wise call (`member`, see
|
|
92
|
+
# `FacetDistribution.select`) answers only a genuine match, never the first-overload fallback.
|
|
93
|
+
# rubocop:disable-next Metrics/ParameterLists -- the selection inputs plus the member-wise flag.
|
|
94
|
+
def select_declared(method_definition, arg_types, self_type, instance_type, type_vars, block_required,
|
|
95
|
+
environment, member)
|
|
96
|
+
declared = method_definition.method_types
|
|
97
|
+
return [] if declared.empty?
|
|
98
98
|
|
|
99
99
|
# `rigor:v1:param: <name> <refinement>` annotations on this method override the RBS-declared
|
|
100
100
|
# parameter type at the matching name. The map is consumed inside `accepts_param?` so overload
|
|
@@ -104,17 +104,18 @@ module Rigor
|
|
|
104
104
|
# Pre-sort: demote overloads whose param class is a disjoint sibling of the receiver class (e.g.
|
|
105
105
|
# `Integer#+(BigDecimal) -> BigDecimal` from the `bigdecimal` RBS reopen). Honors the coerce
|
|
106
106
|
# convention so `5 + ?` for unknown `?` resolves to the receiver-class-preserving arm rather than
|
|
107
|
-
# an arbitrary sibling-class arm that only wins by overload-list position.
|
|
108
|
-
|
|
107
|
+
# an arbitrary sibling-class arm that only wins by overload-list position. The proven pass keeps
|
|
108
|
+
# the declared order (see `run_selection_passes`).
|
|
109
|
+
overloads = ReceiverAffinity.reorder(declared, self_type: self_type, environment: environment)
|
|
109
110
|
|
|
110
111
|
# One keyword bundle for the pass pipeline (see `run_selection_passes`); a block retry rebuilds it
|
|
111
112
|
# with the block flag cleared. Built once and passed positionally -- the previous lambda plus a
|
|
112
113
|
# `**shared` splat per pass allocated three objects per selection (#775).
|
|
113
114
|
shared = { arg_types: arg_types, self_type: self_type, instance_type: instance_type,
|
|
114
115
|
type_vars: type_vars, block_required: block_required, param_overrides: param_overrides,
|
|
115
|
-
alias_expander: environment&.rbs_loader }
|
|
116
|
+
alias_expander: environment&.rbs_loader, environment: environment }
|
|
116
117
|
|
|
117
|
-
matches = run_selection_passes(overloads, shared)
|
|
118
|
+
matches = run_selection_passes(declared, overloads, shared)
|
|
118
119
|
return matches unless matches.empty?
|
|
119
120
|
|
|
120
121
|
# A block at the call site that no block-declaring overload matched: Ruby ignores a block handed
|
|
@@ -123,9 +124,10 @@ module Rigor
|
|
|
123
124
|
# `define_command(:x) do … end` against `def define_command: (Symbol) -> Symbol`) degraded to
|
|
124
125
|
# `Dynamic[Top]` — and on a self-send suppressed the whole method's return type.
|
|
125
126
|
if block_required
|
|
126
|
-
matches = run_selection_passes(overloads, shared.merge(block_required: false))
|
|
127
|
+
matches = run_selection_passes(declared, overloads, shared.merge(block_required: false))
|
|
127
128
|
return matches unless matches.empty?
|
|
128
129
|
end
|
|
130
|
+
return [] if member
|
|
129
131
|
|
|
130
132
|
# No (usable) block at the call site: prefer an overload that does not REQUIRE a block over
|
|
131
133
|
# `overloads.first`. Methods like `Array#filter` / `Enumerable#map` declare the block-bearing
|
|
@@ -150,18 +152,30 @@ module Rigor
|
|
|
150
152
|
class << self
|
|
151
153
|
private
|
|
152
154
|
|
|
153
|
-
#
|
|
154
|
-
# - Pass 1 (strict): skipped when any arg is `
|
|
155
|
-
# an untyped arg accepts every param indiscriminately and would let pass 1
|
|
156
|
-
# strict overload (e.g. `Regexp#=~(nil) -> nil` over the
|
|
155
|
+
# Overload search after pass 0 (`find_proven_overload`):
|
|
156
|
+
# - Pass 1 (strict): skipped when any arg is imprecise (`imprecise_arg?`), because gradual
|
|
157
|
+
# acceptance against an untyped arg accepts every param indiscriminately and would let pass 1
|
|
158
|
+
# lock in an arbitrary strict overload (e.g. `Regexp#=~(nil) -> nil` over the
|
|
159
|
+
# `(::interned?) -> Integer?` overload).
|
|
157
160
|
# - Pass 1.5 (alias-resolved): consults each `RBS::Types::Alias`'s strict arm so e.g.
|
|
158
161
|
# `Array#*(int)` wins over the `Array#*(string) -> String` overload for Integer args.
|
|
159
162
|
# - Pass 2 (gradual): the original gradual matcher so overloads that legitimately rely on
|
|
160
163
|
# duck-typed params still resolve when nothing stricter applies.
|
|
161
164
|
# `shared` is the caller-assembled keyword bundle for `find_matching_overload` — hash-shaped
|
|
162
165
|
# because the pass pipeline forwards it twice and RuboCop's parameter-list budget is real.
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
#
|
|
167
|
+
# Pass 0 (#1344) comes first and reads the overloads in declared order; every later pass reads them
|
|
168
|
+
# in receiver-affinity order (`ReceiverAffinity`). RBS resolves two arms that both take an argument by
|
|
169
|
+
# declaration order: `Rational#+` declares `(Float) -> Float` before `(Numeric) -> Rational`, and the
|
|
170
|
+
# affinity order, which moves the `(Numeric)` arm first, typed `Rational(1, 2) + 0.5` as `Rational`
|
|
171
|
+
# where Ruby answers `1.0`. The affinity order stays for every call pass 0 declines: read in declared
|
|
172
|
+
# order, the strict pass let the `bigdecimal` reopen's `(BigDecimal) -> BigDecimal` arm, which it
|
|
173
|
+
# matches on a `maybe`, take `Integer#+` of a `bot`, a `Dynamic[Integer | Float | …]` or an
|
|
174
|
+
# unloadable class (612 call sites across the survey corpus).
|
|
175
|
+
def run_selection_passes(declared, overloads, shared)
|
|
176
|
+
# Unreordered, pass 0 can only pick what the strict pass picks, so it is skipped (#1344's allocations).
|
|
177
|
+
proven = find_proven_overload(declared, shared) unless overloads.equal?(declared)
|
|
178
|
+
strict = proven || find_matching_overload(overloads, shared, strict: true)
|
|
165
179
|
return strict unless strict.empty?
|
|
166
180
|
|
|
167
181
|
alias_hit = find_matching_overload_via_aliases(
|
|
@@ -170,15 +184,33 @@ module Rigor
|
|
|
170
184
|
return [alias_hit] if alias_hit
|
|
171
185
|
|
|
172
186
|
# Pass 2, array-valued. With every argument carrying real type information the first gradual
|
|
173
|
-
# match keeps its historical single-winner contract. With
|
|
187
|
+
# match keeps its historical single-winner contract. With an imprecise argument in play the
|
|
174
188
|
# matches are indistinguishable by types — position alone would pick — so ALL of them come back
|
|
175
189
|
# and the dispatch layer unions their returns (#521).
|
|
176
190
|
matches = find_matching_overload(overloads, shared, strict: false)
|
|
177
|
-
return matches.first(1) unless shared[:arg_types].any? { |t|
|
|
191
|
+
return matches.first(1) unless shared[:arg_types].any? { |t| imprecise_arg?(t) }
|
|
178
192
|
|
|
179
193
|
matches
|
|
180
194
|
end
|
|
181
195
|
|
|
196
|
+
# Pass 0. With every argument a plain value (a `Constant`, or a `Nominal` with no type arguments), the
|
|
197
|
+
# first overload in declared order that the arguments do not rule out is the one RBS would take, and
|
|
198
|
+
# the pass takes it only when that is certain: every parameter is a lone class naming its argument's
|
|
199
|
+
# own class, and accepts it with a `yes`. Any doubt declines to the affinity-ordered passes. A `maybe`
|
|
200
|
+
# is doubt — the analyzer process cannot load a project class, so a declared `(Base)` only maybe
|
|
201
|
+
# takes a `Sub`, and taking a later `(top)` arm instead typed the call wrong. So is a union or
|
|
202
|
+
# supertype parameter, which takes the argument through a declaration upstream RBS can get wrong:
|
|
203
|
+
# `Rational#divmod`'s `(Integer | Float | Rational) -> [Integer, Rational]` for a Float remainder.
|
|
204
|
+
def find_proven_overload(declared, shared)
|
|
205
|
+
args = shared[:arg_types]
|
|
206
|
+
return nil unless ProvenOverload.applies?(args)
|
|
207
|
+
|
|
208
|
+
index = declared.index { |mt| engages_block_shape?(mt, shared[:block_required]) && matches?(mt, shared) }
|
|
209
|
+
first = index && declared[index]
|
|
210
|
+
proven = first && strictly_typed_params?(first, args.size) && matches?(first, shared, strict: :proven)
|
|
211
|
+
[first] if proven && !ProvenOverload.contested?(declared, index, args, shared[:environment])
|
|
212
|
+
end
|
|
213
|
+
|
|
182
214
|
# The shared "no overload matched" answer; every consumer only reads the list.
|
|
183
215
|
NO_MATCH = [].freeze
|
|
184
216
|
private_constant :NO_MATCH
|
|
@@ -187,7 +219,7 @@ module Rigor
|
|
|
187
219
|
# type_vars, block_required, param_overrides, alias_expander).
|
|
188
220
|
def find_matching_overload(overloads, shared, strict:)
|
|
189
221
|
arg_types = shared[:arg_types]
|
|
190
|
-
return NO_MATCH if strict && arg_types.any? { |t|
|
|
222
|
+
return NO_MATCH if strict && arg_types.any? { |t| imprecise_arg?(t) }
|
|
191
223
|
|
|
192
224
|
block_required = shared[:block_required]
|
|
193
225
|
# Strict keeps its historical first-match short-circuit (a dispatch hot path); the gradual
|
|
@@ -196,7 +228,7 @@ module Rigor
|
|
|
196
228
|
found = overloads.find do |method_type|
|
|
197
229
|
engages_block_shape?(method_type, block_required) &&
|
|
198
230
|
strictly_typed_params?(method_type, arg_types.size) &&
|
|
199
|
-
matches?(method_type, shared)
|
|
231
|
+
matches?(method_type, shared, strict: true)
|
|
200
232
|
end
|
|
201
233
|
return found ? [found] : NO_MATCH
|
|
202
234
|
end
|
|
@@ -218,8 +250,17 @@ module Rigor
|
|
|
218
250
|
# Treats the literal `untyped` carrier (`Dynamic[Top]`) as too imprecise to drive a strict-pass
|
|
219
251
|
# match. Other `Dynamic`-wrapped types with a concrete static facet carry enough information to
|
|
220
252
|
# pick a sensible overload.
|
|
221
|
-
def untyped_arg?(type)
|
|
222
|
-
|
|
253
|
+
def untyped_arg?(type) = type.is_a?(Type::Dynamic) && type.static_facet.is_a?(Type::Top)
|
|
254
|
+
|
|
255
|
+
# Issue #1021 — the strict and alias passes must not discriminate on an argument whose type
|
|
256
|
+
# cannot rule an overload out: the bare untyped carrier, or a union with an untyped member. That
|
|
257
|
+
# member may reach any overload at runtime, so a strict match is decided by the union's other
|
|
258
|
+
# members alone — `Dynamic[top] | nil` pinned `Regexp#match?(nil) -> false` and typed a live
|
|
259
|
+
# predicate as the literal `false`. The gradual pass still accepts against the whole union, so
|
|
260
|
+
# `Dynamic[top] | nil` keeps both `match?` overloads and the #521 join answers `Dynamic[bool]`.
|
|
261
|
+
# A `Dynamic` with a concrete static facet stays out: its facet discriminates.
|
|
262
|
+
def imprecise_arg?(type)
|
|
263
|
+
untyped_arg?(type) || (type.is_a?(Type::Union) && type.members.any? { |member| untyped_arg?(member) })
|
|
223
264
|
end
|
|
224
265
|
|
|
225
266
|
# Pass 1.5: for arity-compatible overloads whose every positional param is either a strict
|
|
@@ -232,7 +273,7 @@ module Rigor
|
|
|
232
273
|
# Issue #521 — an untyped argument "maybe"-accepts EVERY alias's strict arm, so it cannot
|
|
233
274
|
# discriminate between overloads here any more than in the strict pass; without this guard a
|
|
234
275
|
# Dynamic arg pinned `Array#*(string) -> String` purely by declaration order.
|
|
235
|
-
return nil if arg_types.any? { |t|
|
|
276
|
+
return nil if arg_types.any? { |t| imprecise_arg?(t) }
|
|
236
277
|
|
|
237
278
|
overloads.find do |method_type|
|
|
238
279
|
next false unless engages_block_shape?(method_type, block_required)
|
|
@@ -327,7 +368,8 @@ module Rigor
|
|
|
327
368
|
end
|
|
328
369
|
|
|
329
370
|
# `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
|
|
330
|
-
|
|
371
|
+
# `strict:` is `false` (gradual), `true` (strict pass) or `:proven` (pass 0; see `find_proven_overload`).
|
|
372
|
+
def matches?(method_type, shared, strict: false)
|
|
331
373
|
return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
|
|
332
374
|
|
|
333
375
|
arg_types = shared[:arg_types]
|
|
@@ -335,17 +377,7 @@ module Rigor
|
|
|
335
377
|
return false unless arity_compatible?(fun, arg_types.size)
|
|
336
378
|
|
|
337
379
|
params = positional_params_for(fun, arg_types.size)
|
|
338
|
-
each_param_accepts?(params, arg_types)
|
|
339
|
-
accepts_param?(
|
|
340
|
-
param,
|
|
341
|
-
arg,
|
|
342
|
-
self_type: shared[:self_type],
|
|
343
|
-
instance_type: shared[:instance_type],
|
|
344
|
-
type_vars: shared[:type_vars],
|
|
345
|
-
param_overrides: shared[:param_overrides],
|
|
346
|
-
alias_expander: shared[:alias_expander]
|
|
347
|
-
)
|
|
348
|
-
end
|
|
380
|
+
each_param_accepts?(params, arg_types) { |param, arg| accepts_param?(param, arg, shared, strict) }
|
|
349
381
|
end
|
|
350
382
|
|
|
351
383
|
# Slice 4 phase 2c does not pass keyword arguments through the call site (caller passes only
|
|
@@ -401,14 +433,14 @@ module Rigor
|
|
|
401
433
|
head
|
|
402
434
|
end
|
|
403
435
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
param_type = param_overrides[param.name] || RbsTypeTranslator.translate(
|
|
436
|
+
# `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
|
|
437
|
+
def accepts_param?(param, arg, shared, strict)
|
|
438
|
+
param_type = shared[:param_overrides][param.name] || RbsTypeTranslator.translate(
|
|
407
439
|
param.type,
|
|
408
|
-
self_type: self_type,
|
|
409
|
-
instance_type: instance_type,
|
|
410
|
-
type_vars: type_vars,
|
|
411
|
-
alias_expander: alias_expander
|
|
440
|
+
self_type: shared[:self_type],
|
|
441
|
+
instance_type: shared[:instance_type],
|
|
442
|
+
type_vars: shared[:type_vars],
|
|
443
|
+
alias_expander: shared[:alias_expander]
|
|
412
444
|
)
|
|
413
445
|
# An `untyped` arg gradually accepts against every param, so a value-pinning param would be
|
|
414
446
|
# "matched" with zero evidence and its value-precise return (`(nil) -> []`) would beat broader
|
|
@@ -418,6 +450,14 @@ module Rigor
|
|
|
418
450
|
return false if untyped_arg?(arg) && value_pinning?(param_type)
|
|
419
451
|
|
|
420
452
|
result = param_type.accepts(arg, mode: :gradual)
|
|
453
|
+
return result.yes? && ProvenOverload.names_arg_class?(param_type, arg) if strict == :proven
|
|
454
|
+
|
|
455
|
+
# A record's `maybe` for a `Hash` with a gradual arm or an open shape is no evidence for the overload:
|
|
456
|
+
# with `({ a: Integer }) -> Integer | (Hash[Symbol, untyped]) -> String`, `{ **o, b: 2 }` has a key the
|
|
457
|
+
# closed record forbids, yet the first overload won by list position. The gradual pass still takes it.
|
|
458
|
+
# A `maybe` that survives with those records answering `no` has another source, and still counts.
|
|
459
|
+
return false if strict && result.maybe? && Acceptance.maybe_rests_on_unread_hash?(param_type, arg)
|
|
460
|
+
|
|
421
461
|
result.yes? || result.maybe?
|
|
422
462
|
end
|
|
423
463
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
require_relative "singleton_folding"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module MethodDispatcher
|
|
9
|
+
# `Process` class-method calls that read flow state.
|
|
10
|
+
#
|
|
11
|
+
# `Process.last_status` answers the thread's last child status, the slot `$?` reads (both are
|
|
12
|
+
# `rb_last_status_get` in CRuby's `process.c`). `data/core_overlay/process.rbs` declares it
|
|
13
|
+
# `() -> Process::Status?`, which is right where nothing is known. But past a statement that certainly
|
|
14
|
+
# ran a subprocess the flow engine has already bound `$?` to a non-nil `Process::Status`
|
|
15
|
+
# ({Inference::LastStatus}); answering the declared return there would put back the possible nil
|
|
16
|
+
# receiver the binding exists to remove as soon as the code reaches the status through the method
|
|
17
|
+
# rather than the global (`system("make"); Process.last_status.success?`). The same reasoning, for
|
|
18
|
+
# `Regexp.last_match` and `$~`, is {RegexpFolding.fold_last_match}.
|
|
19
|
+
#
|
|
20
|
+
# A bound `$?` is answered as it stands, so the method reads what the global reads in every body the
|
|
21
|
+
# binding rules reach, `Dynamic[top]` in a `define_method` body included. The consult never invents a
|
|
22
|
+
# binding: where `$?` is unbound (rescue clauses, closures, thread roots, joins with a path that ran no
|
|
23
|
+
# subprocess) it declines to the declaration. It also declines an argument-bearing call, whose arity
|
|
24
|
+
# the RBS tier reports.
|
|
25
|
+
#
|
|
26
|
+
# A project that defines `Process.last_status` itself answers `Dynamic[top]`: its method may return
|
|
27
|
+
# anything, and declining would hand the call to the overlay's declaration, which outranks a
|
|
28
|
+
# discovered method. A definition the discovery pass does not record — `def Process.last_status` outside
|
|
29
|
+
# `module Process`, `define_singleton_method`, `alias_method`, a prepended module — keeps the binding's answer.
|
|
30
|
+
module ProcessFolding
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# @return the answer, or nil to defer.
|
|
34
|
+
def try_dispatch(context)
|
|
35
|
+
return nil unless SingletonFolding.receiver?(context.receiver, "Process")
|
|
36
|
+
return nil unless context.method_name == :last_status && context.args.empty?
|
|
37
|
+
|
|
38
|
+
scope = context.scope
|
|
39
|
+
return nil if scope.nil?
|
|
40
|
+
return Type::Combinator.untyped if project_defines_last_status?(scope)
|
|
41
|
+
|
|
42
|
+
scope.global(:$?)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def project_defines_last_status?(scope)
|
|
46
|
+
return true if scope.discovered_method?("Process", :last_status, :singleton)
|
|
47
|
+
return true if scope.discovered_singleton_def_nodes["Process"]&.key?(:last_status)
|
|
48
|
+
|
|
49
|
+
patched = scope.environment&.project_patched_methods
|
|
50
|
+
!patched.nil? && !patched.lookup(class_name: "Process", method_name: :last_status, kind: :singleton).nil?
|
|
51
|
+
end
|
|
52
|
+
private_class_method :project_defines_last_status?
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# The value tests of `OverloadSelector`'s pass 0 (#1344), which reads the overloads in declared order and
|
|
9
|
+
# takes the first one plain-value arguments do not rule out, when that one names each argument's own class.
|
|
10
|
+
#
|
|
11
|
+
# Acceptance reads class relations from the analyzer's own process, which cannot load a project class or
|
|
12
|
+
# module, so it can rule an arm out wrongly: `(Printable)` against `1`, where project RBS includes
|
|
13
|
+
# `Printable` into `Integer`, answers `no`, and taking the `(Integer)` arm declared after it typed the call
|
|
14
|
+
# as that arm's return and fired `call.undefined-method` on correct code. {.contested?} therefore trusts
|
|
15
|
+
# an earlier arm to be out only when it can prove it: every positional parameter, rest included, is a plain
|
|
16
|
+
# class (through `?` and `|`), and both orderings — `Environment#class_ordering`, which asks the host class
|
|
17
|
+
# registry first, and the RBS loader's own — call it disjoint from every argument's class. A type alias,
|
|
18
|
+
# interface or variable proves nothing, and the host registry alone calls `Integer` and `Enumerable`
|
|
19
|
+
# disjoint where project RBS includes one into the other. Nor does a module, or a name Rigor stubbed because
|
|
20
|
+
# no RBS declares it: the source can include a module into the argument's class where no RBS says so, and
|
|
21
|
+
# neither ordering sees a source-only `include` (#1351). A class cannot be included, so a declared class
|
|
22
|
+
# stays provable.
|
|
23
|
+
module ProvenOverload
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# Every argument is a plain value: a `Constant`, or a `Nominal` with no type arguments.
|
|
27
|
+
def applies?(arg_types)
|
|
28
|
+
!arg_types.empty? && arg_types.all? do |arg|
|
|
29
|
+
arg.is_a?(Type::Constant) || (arg.is_a?(Type::Nominal) && arg.type_args.empty?)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def names_arg_class?(param, arg) = param.is_a?(Type::Nominal) && param.class_name == class_name_of(arg)
|
|
34
|
+
|
|
35
|
+
def class_name_of(arg) = arg.is_a?(Type::Constant) ? arg.value.class.name : arg.class_name
|
|
36
|
+
|
|
37
|
+
# Whether an overload declared before `declared[index]` may take the arguments after all.
|
|
38
|
+
def contested?(declared, index, arg_types, environment)
|
|
39
|
+
loader = environment&.rbs_loader
|
|
40
|
+
return true if loader.nil?
|
|
41
|
+
|
|
42
|
+
arg_names = arg_types.map { |arg| class_name_of(arg) }
|
|
43
|
+
(0...index).any? { |position| !proven_out?(declared[position], arg_names, environment, loader) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def proven_out?(method_type, arg_names, environment, loader)
|
|
47
|
+
fun = method_type.type
|
|
48
|
+
return false unless fun.respond_to?(:required_positionals)
|
|
49
|
+
|
|
50
|
+
params = fun.required_positionals + fun.optional_positionals + fun.trailing_positionals
|
|
51
|
+
params += [fun.rest_positionals] if fun.rest_positionals
|
|
52
|
+
params.all? { |param| disjoint_param?(param.type, arg_names, environment, loader) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def disjoint_param?(rbs_type, arg_names, environment, loader)
|
|
56
|
+
case rbs_type
|
|
57
|
+
when RBS::Types::ClassInstance
|
|
58
|
+
name = rbs_type.name.to_s.delete_prefix("::")
|
|
59
|
+
return false if loader.rbs_module?(name) || loader.synthesized_type_names.include?(name)
|
|
60
|
+
|
|
61
|
+
arg_names.all? do |arg|
|
|
62
|
+
environment.class_ordering(arg, name) == :disjoint && loader.class_ordering(arg, name) == :disjoint
|
|
63
|
+
end
|
|
64
|
+
when RBS::Types::Optional then disjoint_param?(rbs_type.type, arg_names, environment, loader)
|
|
65
|
+
when RBS::Types::Union then rbs_type.types.all? { |member| disjoint_param?(member, arg_names, environment, loader) }
|
|
66
|
+
else false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|