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
|
@@ -35,23 +35,28 @@ module Rigor
|
|
|
35
35
|
# argument and the value the last.
|
|
36
36
|
HASH_CONTENT_ADDERS = %i[[]= store].to_set.freeze
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
38
|
+
# Every method name that adds to a collection's CONTENT — the evidence the joins read, and the
|
|
39
|
+
# gate the straight-line path types its arguments behind. A String carries no element
|
|
40
|
+
# parameter, so it has no adders of its own; `StatementEvaluator::CONTENT_MUTATORS` adds the
|
|
41
|
+
# String mutators to what its scans count.
|
|
42
|
+
CONTENT_ADDERS = (ARRAY_CONTENT_ADDERS | HASH_CONTENT_ADDERS).freeze
|
|
43
43
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
|
|
44
|
+
# The probes {#could_be_range?} passes to `accepts` — interned because the questions are
|
|
45
|
+
# asked once per `[]=` index member.
|
|
46
|
+
RANGE_INDEX_PROBE = Type::Combinator.nominal_of("Range")
|
|
47
|
+
OBJECT_INDEX_PROBE = Type::Combinator.nominal_of("Object")
|
|
48
|
+
ARRAY_PROBE = Type::Combinator.nominal_of("Array")
|
|
49
|
+
private_constant :RANGE_INDEX_PROBE, :OBJECT_INDEX_PROBE, :ARRAY_PROBE
|
|
47
50
|
|
|
48
51
|
module_function
|
|
49
52
|
|
|
50
53
|
# The element types a single content-mutator call introduces into an Array, given the
|
|
51
54
|
# per-argument types (already typed by the caller in the scope the arguments are evaluated
|
|
52
55
|
# in). `concat`/`replace` take collection arguments, so their element evidence is the
|
|
53
|
-
# arguments' OWN element types unioned; the
|
|
54
|
-
# `[]`
|
|
56
|
+
# arguments' OWN element types unioned; the `[]=` splice forms (`arr[i, n] = other` /
|
|
57
|
+
# `arr[range] = other`) store their value's elements the same way and read it through the
|
|
58
|
+
# same unwrap (issue #1140). The rest append the argument values directly. Returns `[]` when
|
|
59
|
+
# there is no element evidence (e.g. a `<<` with no resolvable arg).
|
|
55
60
|
def array_added_elements(method_name, arg_types)
|
|
56
61
|
return [] if arg_types.empty?
|
|
57
62
|
|
|
@@ -62,21 +67,27 @@ module Rigor
|
|
|
62
67
|
# `insert(index, *objs)` — first arg is the position.
|
|
63
68
|
arg_types.drop(1)
|
|
64
69
|
when :[]=
|
|
65
|
-
# `arr[i] = v`
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
[arg_types.last]
|
|
70
|
+
# `arr[i] = v` stores `v` as ONE element — the index arguments precede the value.
|
|
71
|
+
# The splice forms store the value's ELEMENTS instead (issue #1140): `a[0, 2] = [1, 2]`
|
|
72
|
+
# puts `Integer`s in the receiver, not an `Array[Integer]`. An index the engine cannot
|
|
73
|
+
# classify (`a[x] = v` with `x` untyped, a `Range | Integer` union, or a splat that
|
|
74
|
+
# may vanish inside the brackets) may be either form, so both readings join — the
|
|
75
|
+
# union is a superset of the truth either way.
|
|
76
|
+
case index_store_form(arg_types)
|
|
77
|
+
when :splice then splice_stored_elements(arg_types.last)
|
|
78
|
+
when :either then [arg_types.last] + splice_stored_elements(arg_types.last)
|
|
79
|
+
else [arg_types.last]
|
|
80
|
+
end
|
|
74
81
|
when :fill
|
|
75
82
|
# `fill(value)` — only the no-block single-value form adds a concrete element; block /
|
|
76
83
|
# range forms are conservatively ignored (the arity-forget already widened the binding).
|
|
77
84
|
arg_types.size == 1 ? arg_types : []
|
|
78
|
-
|
|
85
|
+
when :<<, :push, :append, :prepend, :unshift
|
|
79
86
|
arg_types
|
|
87
|
+
else
|
|
88
|
+
# Not an Array adder — a String mutator a content scan counted (`x.sub!("a", "b")` on an
|
|
89
|
+
# `Array | String` capture): its arguments are not elements.
|
|
90
|
+
[]
|
|
80
91
|
end
|
|
81
92
|
end
|
|
82
93
|
|
|
@@ -122,6 +133,23 @@ module Rigor
|
|
|
122
133
|
with_residue(Type::Combinator.nominal_of("Hash", type_args: [key_t, value_t]), hash_residue(pre_state))
|
|
123
134
|
end
|
|
124
135
|
|
|
136
|
+
# Builds the continuation of a pre-state that carries BOTH an Array and a Hash member, from the
|
|
137
|
+
# pairs and the elements the body stores. Each side is joined with its own class's evidence, and
|
|
138
|
+
# every member neither side absorbs survives beside the Hash carrier ({#hash_residue}).
|
|
139
|
+
#
|
|
140
|
+
# Joined as one carrier, the other class's member was residue and survived exactly as the seed
|
|
141
|
+
# had it: an unwidened literal `Tuple` / `HashShape`, so what the body did to it was gone — `x =
|
|
142
|
+
# gets ? [1] : { a: 1 }; [0].each { x.map!(&:to_s) if x.is_a?(Array); x[:k] = 2 if
|
|
143
|
+
# x.is_a?(Hash) }` read `[1]`, and `x.first.upcase` drew `undefined method` on correct code. The
|
|
144
|
+
# caller routes the evidence: a store may reach either member, and only the caller sees its
|
|
145
|
+
# index (`StatementEvaluator#mixed_content_evidence`).
|
|
146
|
+
def join_mixed_content(pre_state, added_pairs, added_elements)
|
|
147
|
+
members = pre_state.is_a?(Type::Union) ? pre_state.members : [pre_state]
|
|
148
|
+
arrays, rest = members.partition { |member| array_residue(member).empty? }
|
|
149
|
+
array = join_array_content(Type::Combinator.union(*arrays), added_elements)
|
|
150
|
+
Type::Combinator.union(array, join_hash_content(Type::Combinator.union(*rest), added_pairs))
|
|
151
|
+
end
|
|
152
|
+
|
|
125
153
|
# ----------------------------------------------------------------
|
|
126
154
|
# Union residue — the members the rederived carrier does NOT stand for (issue #631).
|
|
127
155
|
#
|
|
@@ -277,15 +305,158 @@ module Rigor
|
|
|
277
305
|
type.is_a?(Type::Union) ? type.members : [type]
|
|
278
306
|
end
|
|
279
307
|
|
|
308
|
+
# How an `arr[...] = v` call's index arguments place the stored value: `:splice` — the
|
|
309
|
+
# value's ELEMENTS land in the receiver (`arr[i, n] = v` takes two index slots,
|
|
310
|
+
# `arr[range] = v` takes a Range); `:element` — the value itself is one stored element
|
|
311
|
+
# (`arr[i] = v`); `:either` — the store may be either form.
|
|
312
|
+
#
|
|
313
|
+
# A splat inside the brackets is marked `nil` by the caller — its expansion can change the
|
|
314
|
+
# store's arity (`a[0, *xs] = v` stores `v` itself when `xs` is empty and `v`'s elements
|
|
315
|
+
# otherwise). An ordinary argument counts as a provable index however untyped it is:
|
|
316
|
+
# `a[i, n] = v` is a splice at every binding of `i` and `n`. Two or more provable indices
|
|
317
|
+
# are therefore always a splice however a splat expands; one provable index beside a splat
|
|
318
|
+
# reduces to the Range question (extra expansion only reaches an arity error, never a
|
|
319
|
+
# scalar store); zero provable indices leaves the form to whatever the splat yields.
|
|
320
|
+
def index_store_form(arg_types)
|
|
321
|
+
leading = arg_types[0...-1]
|
|
322
|
+
definite = leading.compact
|
|
323
|
+
return :splice if definite.size >= 2
|
|
324
|
+
return :either if definite.empty?
|
|
325
|
+
return single_index_form(definite.first) if definite.size == leading.size
|
|
326
|
+
|
|
327
|
+
range_index?(definite.first) ? :splice : :either
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# The one-index question: a Range carrier is a splice, a definite scalar an element store,
|
|
331
|
+
# and anything between — a union straddling both, a broad index that may still hold a
|
|
332
|
+
# Range at runtime — may be either.
|
|
333
|
+
def single_index_form(index_type)
|
|
334
|
+
members = union_members(index_type)
|
|
335
|
+
return :splice if members.all? { |m| definite_range?(m) }
|
|
336
|
+
return :element if members.none? { |m| could_be_range?(m) }
|
|
337
|
+
|
|
338
|
+
:either
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# True when the index position PROVABLY holds a Range: a Range carrier {#range_index?}
|
|
342
|
+
# recognizes, or a member acceptance proves a subtype of `Range` — a loaded
|
|
343
|
+
# `class MyRange < Range` answers `yes` there where the name check cannot see it. An
|
|
344
|
+
# unresolvable subclass answers `maybe`, which correctly declines here and still reaches
|
|
345
|
+
# {#could_be_range?}'s `either` reading. A gradual member accepts in BOTH directions
|
|
346
|
+
# (`Range` accepts it optimistically), so it must be excluded before asking — an untyped
|
|
347
|
+
# index may hold a scalar just as well and stays `either`. The exclusion reads through a
|
|
348
|
+
# `Difference` / `Refined` to its base: acceptance projects the wrapper onto the base too,
|
|
349
|
+
# so a `non-nil` or refined `Dynamic` answers `yes` there exactly like the bare gradual
|
|
350
|
+
# while still being free to hold a scalar at runtime.
|
|
351
|
+
def definite_range?(member)
|
|
352
|
+
return true if range_index?(member)
|
|
353
|
+
|
|
354
|
+
case member
|
|
355
|
+
when Type::Dynamic, Type::Top then false
|
|
356
|
+
when Type::Difference, Type::Refined then definite_range?(member.base)
|
|
357
|
+
else member.respond_to?(:accepts) && RANGE_INDEX_PROBE.accepts(member).yes?
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# True when the index position MAY hold a Range at runtime: a definite Range carrier
|
|
362
|
+
# (`Constant[0..1]` accepts only its own value, so acceptance alone cannot see it),
|
|
363
|
+
# `Dynamic`, a carrier the engine cannot answer for, and every type related to
|
|
364
|
+
# `Nominal[Range]` in EITHER direction — `Object`/`Enumerable`/`top` above it, a
|
|
365
|
+
# project `class MyRange < Range` below it (an unresolvable class name answers `maybe`,
|
|
366
|
+
# never `no`, so subclass-ness cannot be ruled out).
|
|
367
|
+
#
|
|
368
|
+
# `no` in both directions is a real disjointness only when the member's values are
|
|
369
|
+
# provably `Object` instances — a fixed-class value set that single inheritance keeps
|
|
370
|
+
# from ever being a Range (`Integer`, a non-Range `Constant`, a `Tuple`). A MODULE
|
|
371
|
+
# member fails that probe (`Comparable`'s ancestors exclude `Object` — a module has no
|
|
372
|
+
# superclass chain), and rightly so: `class MyRange < Range; include Comparable; end`
|
|
373
|
+
# satisfies the annotation and still splices at runtime.
|
|
374
|
+
def could_be_range?(member)
|
|
375
|
+
return true if range_index?(member)
|
|
376
|
+
|
|
377
|
+
case member
|
|
378
|
+
when Type::Intersection
|
|
379
|
+
# An intersected value sits in EVERY member's value set, so it can be a Range only
|
|
380
|
+
# when every member allows one — `String & Comparable` cannot be (a String never
|
|
381
|
+
# is), `Object & Comparable` can (`MyRange < Range; include Comparable` satisfies
|
|
382
|
+
# both). Asked member-wise because the `Object` probe below answers yes when ANY
|
|
383
|
+
# intersected member accepts it.
|
|
384
|
+
member.members.all? { |part| could_be_range?(part) }
|
|
385
|
+
when Type::Maybe then could_be_range?(member.value_type)
|
|
386
|
+
else
|
|
387
|
+
return true unless member.respond_to?(:accepts)
|
|
388
|
+
return true unless member.accepts(RANGE_INDEX_PROBE).no? &&
|
|
389
|
+
RANGE_INDEX_PROBE.accepts(member).no?
|
|
390
|
+
|
|
391
|
+
!OBJECT_INDEX_PROBE.accepts(member).yes?
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Element types a splice RHS adds to the receiver, read member-wise. Ruby splices an
|
|
396
|
+
# Array RHS (`a[0, 1] = [1, 2]` puts `Integer`s in — a bare `Nominal[Array]` splices
|
|
397
|
+
# elements the signature left untyped, which are unknown rather than none), and stores a
|
|
398
|
+
# NON-Array RHS as one element UNLESS the value coerces through `to_ary`, in which case
|
|
399
|
+
# the returned array's elements splice instead (`a[0, 1] = obj` where `obj.to_ary`
|
|
400
|
+
# returns `["x"]` stores `"x"`). `nil` is no exception — `a[i, n] = nil` stores a single
|
|
401
|
+
# nil.
|
|
402
|
+
#
|
|
403
|
+
# Every member that is not an Array carrier keeps a `Dynamic[top]` arm beside itself:
|
|
404
|
+
# `to_ary` is consulted for a NON-Array value, and nothing here can prove the member's
|
|
405
|
+
# class does not define one — a `Nominal` may be a subclass that declares it, and the
|
|
406
|
+
# project may reopen even a non-subclassable core class (ADR-17's patched-methods tier
|
|
407
|
+
# resolves exactly those). The two exact carriers are the ones `Array#[]=` splices
|
|
408
|
+
# without a coercion: a `Tuple` literal's elements, and a `Nominal[Array]`'s own type
|
|
409
|
+
# arguments — an Array object is spliced directly even when its class overrides
|
|
410
|
+
# `to_ary`.
|
|
411
|
+
def splice_stored_elements(value_type)
|
|
412
|
+
union_members(value_type).flat_map do |member|
|
|
413
|
+
base = member
|
|
414
|
+
base = base.base while base.is_a?(Type::Difference) || base.is_a?(Type::Refined)
|
|
415
|
+
|
|
416
|
+
case base
|
|
417
|
+
when Type::Tuple
|
|
418
|
+
base.elements
|
|
419
|
+
when Type::Nominal
|
|
420
|
+
if base.class_name == "Array"
|
|
421
|
+
# `Array#[]=` splices an Array RHS directly — `to_ary` is never consulted
|
|
422
|
+
# for an Array object, even a subclass overriding it — so the elements
|
|
423
|
+
# join exactly (a bare `Array` signature left them unknown, not absent).
|
|
424
|
+
base.type_args.empty? ? [Type::Combinator.untyped] : base.type_args
|
|
425
|
+
elsif ARRAY_PROBE.accepts(base).yes?
|
|
426
|
+
# A definite Array subclass is spliced the same way; its elements are
|
|
427
|
+
# unknown to this seam.
|
|
428
|
+
[Type::Combinator.untyped]
|
|
429
|
+
else
|
|
430
|
+
[member, Type::Combinator.untyped]
|
|
431
|
+
end
|
|
432
|
+
else
|
|
433
|
+
[member, Type::Combinator.untyped]
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# True when an index position holds a Range carrier — a static `Constant<Range>` (`0..1`),
|
|
439
|
+
# a `Nominal[Range]` (`(0..n)`), or a refinement / difference over either. `IntegerRange`
|
|
440
|
+
# and `FloatRange` are scalar number refinements rather than Range objects and correctly
|
|
441
|
+
# decline, as does every ordinary scalar index.
|
|
442
|
+
def range_index?(type)
|
|
443
|
+
case type
|
|
444
|
+
when Type::Constant then type.value.is_a?(::Range)
|
|
445
|
+
when Type::Nominal then type.class_name == "Range"
|
|
446
|
+
when Type::Difference, Type::Refined then range_index?(type.base)
|
|
447
|
+
else false
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
280
451
|
# Element types carried by a collection binding, regardless of which carrier holds them: a
|
|
281
452
|
# `Tuple` lists them, a `Nominal[Array, [E]]` has one element param, a bare `Array` /
|
|
282
453
|
# anything else yields none.
|
|
283
454
|
#
|
|
284
455
|
# A `Difference` reads through to its base: `non-empty-array[T]` holds `T`s, and the seams
|
|
285
456
|
# that read a seed from BEFORE the arity-forget ran (see {#join_array_content}) meet the
|
|
286
|
-
# refinement carrier itself where they used to meet the base the widening had left.
|
|
287
|
-
# it there would hand the continuation the widened base ALONE, with every appended
|
|
288
|
-
# — a wrong type, not a wide one.
|
|
457
|
+
# refinement carrier itself where they used to meet the base the widening had left.
|
|
458
|
+
# Declining it there would hand the continuation the widened base ALONE, with every appended
|
|
459
|
+
# arm missing — a wrong type, not a wide one.
|
|
289
460
|
def collection_element_types(type)
|
|
290
461
|
case type
|
|
291
462
|
when Type::Tuple
|
|
@@ -305,13 +476,15 @@ module Rigor
|
|
|
305
476
|
|
|
306
477
|
# `[keys, values]` evidence from a Hash-ish pre-state binding — a `HashShape` (literal pairs)
|
|
307
478
|
# or a `Nominal[Hash, [K, V]]`. A `Difference` (`non-empty-hash[K, V]`) reads through to its
|
|
308
|
-
# base, as {#collection_element_types} does for the Array side.
|
|
479
|
+
# base, as {#collection_element_types} does for the Array side. An open shape adds a
|
|
480
|
+
# `Dynamic[top]` key and value: its unseen keys may hold anything, and the joined carrier's
|
|
481
|
+
# read of one must not answer a known value (`flags = { debug: true }; flags.default = false`
|
|
482
|
+
# then a store in a block read `flags[:verbose]` as `true`).
|
|
309
483
|
def hash_shape_key_values(type)
|
|
310
484
|
case type
|
|
311
485
|
when Type::HashShape
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
[[key_union_for(type.pairs.keys)], type.pairs.values]
|
|
486
|
+
keys, values = type.pairs.empty? ? [[], []] : [[key_union_for(type.pairs.keys)], type.pairs.values]
|
|
487
|
+
type.open? ? [keys + [Type::Combinator.untyped], values + [Type::Combinator.untyped]] : [keys, values]
|
|
315
488
|
when Type::Nominal
|
|
316
489
|
type.class_name == "Hash" && type.type_args.size == 2 ? [[type.type_args[0]], [type.type_args[1]]] : [[], []]
|
|
317
490
|
when Type::Union
|
|
@@ -4,6 +4,7 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../type"
|
|
6
6
|
require_relative "../source/node_children"
|
|
7
|
+
require_relative "return_barrier"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
9
10
|
module Inference
|
|
@@ -13,15 +14,14 @@ module Rigor
|
|
|
13
14
|
# The inferred type is the union of:
|
|
14
15
|
#
|
|
15
16
|
# - the body's last-statement type, and
|
|
16
|
-
# - the type of every explicit `return value`
|
|
17
|
-
#
|
|
17
|
+
# - the type of every explicit `return value` that exits the method. A `return` inside an ordinary block does
|
|
18
|
+
# (control-flow-analysis.md § "Non-local exits"); one inside a {ReturnBarrier} — a nested `def`, a lambda,
|
|
19
|
+
# or a `define_method` body — does not. The barriers are the ones `StatementEvaluator`'s return sink stops
|
|
20
|
+
# at, so the proposal and `rigor check`'s view of callers agree (issue #1382).
|
|
18
21
|
#
|
|
19
22
|
# Extracted from `Rigor::SigGen::Generator#infer_return_type` so `LineTypeCollector` (`rigor annotate`'s
|
|
20
23
|
# def-line annotator) and the sig-generator share one source of truth.
|
|
21
24
|
module DefReturnTyper
|
|
22
|
-
RETURN_BARRIER_NODES = [Prism::DefNode, Prism::LambdaNode, Prism::BlockNode].freeze
|
|
23
|
-
private_constant :RETURN_BARRIER_NODES
|
|
24
|
-
|
|
25
25
|
module_function
|
|
26
26
|
|
|
27
27
|
def call(def_node, scope_index)
|
|
@@ -60,10 +60,14 @@ module Rigor
|
|
|
60
60
|
|
|
61
61
|
def collect_return_types(node, scope_index, out)
|
|
62
62
|
return unless node.is_a?(Prism::Node)
|
|
63
|
-
return if
|
|
63
|
+
return if ReturnBarrier.node?(node)
|
|
64
64
|
|
|
65
65
|
type_return_node(node, scope_index, out) if node.is_a?(Prism::ReturnNode)
|
|
66
|
-
|
|
66
|
+
# A barrier call's receiver and arguments still run in the method; only its block is cut off.
|
|
67
|
+
barrier_block = node.block if node.is_a?(Prism::CallNode) && ReturnBarrier.block_call?(node)
|
|
68
|
+
node.rigor_each_child do |c|
|
|
69
|
+
collect_return_types(c, scope_index, out) unless c.equal?(barrier_block)
|
|
70
|
+
end
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
def type_return_node(return_node, scope_index, out)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
# Issue #963 — the lexical `self` of a `define_method(:name) { ... }` block.
|
|
8
|
+
#
|
|
9
|
+
# A block has no `self` of its own, so every block body enters with `Scope#entering_opaque_block`
|
|
10
|
+
# and the enclosing class body's `self_type` (`Singleton[C]`) rides along unchanged. For
|
|
11
|
+
# `define_method` that carrier is the wrong side of the class: `Module#define_method` turns the
|
|
12
|
+
# block into an INSTANCE method of the receiver, and Ruby runs its body with `self` bound to the
|
|
13
|
+
# receiving instance. A bare `text` inside the block therefore reaches `C`'s member / attr reader,
|
|
14
|
+
# while `Singleton[C]` answers only `C`'s class methods.
|
|
15
|
+
#
|
|
16
|
+
# The consequence #963 records is #618's veto going the wrong way: `ExpressionTyper#self_type_answers?`
|
|
17
|
+
# asked the singleton side, found nothing, and let a same-named top-level `def text` bind ahead of the
|
|
18
|
+
# reader — typing `text.upcase` inside `class Line < Struct.new(:text); define_method(:shout) { text.upcase }`
|
|
19
|
+
# as the top-level def's `nil`. Narrowing the block body's `self_type` to `Nominal[C]` makes the
|
|
20
|
+
# existing veto ask the instance side, which is the side Ruby dispatches on; nothing in the veto
|
|
21
|
+
# itself changes.
|
|
22
|
+
#
|
|
23
|
+
# The match is deliberately narrow — the receiver must be the class body's own `self`, and that `self`
|
|
24
|
+
# must be a known `Singleton[C]` on the instance side of the class. `Foo.define_method(...)` on some other
|
|
25
|
+
# receiver keeps the unmodelled-self answer, and so does a `class << ...` BODY, where `self` is the
|
|
26
|
+
# singleton class and the call defines a CLASS method: an instance reader is not in that MRO, MRI reaches
|
|
27
|
+
# the top-level `def` there, and Rigor must answer the same. The body is the exclusion, not the lexical
|
|
28
|
+
# singleton frame — a `def` reached from that body has the class object as its `self`, so the call defines
|
|
29
|
+
# an instance method and the narrowing applies. `Scope#singleton_class_body?` is what draws that line, and
|
|
30
|
+
# carrying it on the scope is what lets both block-entry paths apply it identically.
|
|
31
|
+
# A `module_function` module is the one shape where both answers are right at once: MRI makes the block
|
|
32
|
+
# BOTH an instance method, where the reader resolves, and a module function on the module object, where it
|
|
33
|
+
# does not. Narrowing silences the module-function half, which is the false-positive-averse direction
|
|
34
|
+
# AGENTS.md § Implementation Guidelines asks for.
|
|
35
|
+
module DefineMethodBlockSelf
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
# @return the block body's narrowed `self_type`, or `nil` when the call shape does not match.
|
|
39
|
+
def narrow_self_type_for(scope:, call_node:)
|
|
40
|
+
return nil if scope&.singleton_class_body?
|
|
41
|
+
return nil unless define_method_on_lexical_self?(call_node)
|
|
42
|
+
|
|
43
|
+
self_type = scope&.self_type
|
|
44
|
+
return nil unless self_type.is_a?(Type::Singleton)
|
|
45
|
+
|
|
46
|
+
class_name = self_type.class_name
|
|
47
|
+
return nil if class_name.nil?
|
|
48
|
+
|
|
49
|
+
instance_type_for(class_name, scope.environment)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def define_method_on_lexical_self?(call_node)
|
|
53
|
+
return false unless call_node.is_a?(Prism::CallNode)
|
|
54
|
+
return false unless call_node.name == :define_method
|
|
55
|
+
|
|
56
|
+
call_node.receiver.nil? || call_node.receiver.is_a?(Prism::SelfNode)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def instance_type_for(class_name, environment)
|
|
60
|
+
environment&.nominal_for_name(class_name) || Type::Nominal.new(class_name)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -45,7 +45,7 @@ module Rigor
|
|
|
45
45
|
widened = widen_through_path(current_scope.local(read.name), steps, call_node.name, arg_types)
|
|
46
46
|
return current_scope if widened.nil?
|
|
47
47
|
|
|
48
|
-
current_scope.
|
|
48
|
+
current_scope.with_mutated_local(read.name, widened)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# True when `receiver` is an element read whose target element is a literal-shape carrier —
|
|
@@ -62,7 +62,12 @@ module Rigor
|
|
|
62
62
|
# `[<local read>, [step, …]]` for a chain of element reads rooted at a local variable, or
|
|
63
63
|
# `nil` when the expression is anything else. A step is an Integer position (negative counts
|
|
64
64
|
# from the end, as Ruby's own indexing does) or `:all` when the index is not a literal.
|
|
65
|
+
#
|
|
66
|
+
# Every mutation site's receiver is asked, and nearly every one is a plain variable read, so a
|
|
67
|
+
# receiver that is no call answers before the step list is allocated.
|
|
65
68
|
def element_read_path(node)
|
|
69
|
+
return nil unless node.is_a?(Prism::CallNode)
|
|
70
|
+
|
|
66
71
|
steps = []
|
|
67
72
|
cursor = node
|
|
68
73
|
while cursor.is_a?(Prism::CallNode)
|
|
@@ -118,22 +123,29 @@ module Rigor
|
|
|
118
123
|
end
|
|
119
124
|
|
|
120
125
|
# Rebuilds `type` with the element at `steps` widened against `method_name`, or `nil` when
|
|
121
|
-
# nothing along the path widened.
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
# nothing along the path widened. A block, when given, replaces the straight-line widening of the
|
|
127
|
+
# element itself: it receives the element's type and answers the widened one, or `nil` to decline.
|
|
128
|
+
# {UnknownStoreWidening} passes one so a site on an element takes the same unknown-store widening a
|
|
129
|
+
# site on a variable does.
|
|
130
|
+
def widen_through_path(type, steps, method_name, arg_types, &element_widening)
|
|
131
|
+
if steps.empty?
|
|
132
|
+
return element_widening.call(type) if element_widening
|
|
133
|
+
|
|
134
|
+
return MutationWidening.widen_for_mutator(type, method_name, arg_types: arg_types)
|
|
135
|
+
end
|
|
124
136
|
|
|
125
137
|
case type
|
|
126
|
-
when Type::Tuple then widen_tuple_path(type, steps, method_name, arg_types)
|
|
127
|
-
when Type::Union then widen_union_path(type, steps, method_name, arg_types)
|
|
138
|
+
when Type::Tuple then widen_tuple_path(type, steps, method_name, arg_types, &element_widening)
|
|
139
|
+
when Type::Union then widen_union_path(type, steps, method_name, arg_types, &element_widening)
|
|
128
140
|
end
|
|
129
141
|
end
|
|
130
142
|
|
|
131
|
-
def widen_tuple_path(tuple, steps, method_name, arg_types)
|
|
143
|
+
def widen_tuple_path(tuple, steps, method_name, arg_types, &)
|
|
132
144
|
elements = tuple.elements.dup
|
|
133
145
|
rest = steps.drop(1)
|
|
134
146
|
widened_any = false
|
|
135
147
|
tuple_positions(tuple, steps.first).each do |position|
|
|
136
|
-
inner = widen_through_path(elements[position], rest, method_name, arg_types)
|
|
148
|
+
inner = widen_through_path(elements[position], rest, method_name, arg_types, &)
|
|
137
149
|
next if inner.nil?
|
|
138
150
|
|
|
139
151
|
elements[position] = inner
|
|
@@ -142,10 +154,10 @@ module Rigor
|
|
|
142
154
|
widened_any ? Type::Tuple.new(elements) : nil
|
|
143
155
|
end
|
|
144
156
|
|
|
145
|
-
def widen_union_path(union, steps, method_name, arg_types)
|
|
157
|
+
def widen_union_path(union, steps, method_name, arg_types, &)
|
|
146
158
|
widened_any = false
|
|
147
159
|
members = union.members.map do |member|
|
|
148
|
-
widened = widen_through_path(member, steps, method_name, arg_types)
|
|
160
|
+
widened = widen_through_path(member, steps, method_name, arg_types, &)
|
|
149
161
|
next member if widened.nil?
|
|
150
162
|
|
|
151
163
|
widened_any = true
|