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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 96e337b134c8bcb33bf20707e88ffbe16ff98b94513d483043e18432d9bf6328
|
|
4
|
+
data.tar.gz: e46a5af02a699ef2d0ad41e7b8b94e2d5eac02d1085d30fc03831df208ef930e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc7a7cadf53373e66de5ff268b2a44452d42972cbd9c1683825e2809480573bf195d9c26252d0522ddac2ae3f39f9dc665768d61cbb7c6e63ee6d60eaa17b87a
|
|
7
|
+
data.tar.gz: 05b82b8662a6e5b0283a04eca851d0bb6aa1031f55fd1e99525c5b884e433a30b89d519486f6fceebca95e47a306f721766adecc13d4c68134c833e9e525304a
|
data/README.md
CHANGED
|
@@ -231,8 +231,8 @@ rigor docs --list # list every bundled page
|
|
|
231
231
|
|
|
232
232
|
## Status
|
|
233
233
|
|
|
234
|
-
Current release: **`v0.
|
|
235
|
-
`0.
|
|
234
|
+
Current release: **`v0.4.0`** (2026-09-27) — opening the
|
|
235
|
+
`0.4.x` line, continuing the evaluation era opened by `v0.2.0`, the first
|
|
236
236
|
publicly-announced (general / evaluation) release. The line publishes
|
|
237
237
|
an enumerated [compatibility surface](docs/compatibility.md) as a
|
|
238
238
|
minor-non-break trial, rehearsing the contract that hard-freezes at
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Rigor core overlay — supplemental core signatures.
|
|
2
|
+
#
|
|
3
|
+
# --- Enumerable#detect with an ifnone argument ---
|
|
4
|
+
#
|
|
5
|
+
# CRuby binds `detect` and `find` to one C function (`enum.c`:
|
|
6
|
+
# `rb_define_method(rb_mEnumerable, "detect", enum_find, -1)`), so
|
|
7
|
+
# `detect(ifnone)` answers `ifnone.call` when no element matches, exactly as
|
|
8
|
+
# `find(ifnone)` does. Upstream `ruby/rbs` declares the two apart, and only
|
|
9
|
+
# `find` carries the fallback arm:
|
|
10
|
+
#
|
|
11
|
+
# def find: () { (Elem) -> boolish } -> Elem?
|
|
12
|
+
# | () -> ::Enumerator[Elem, Elem?]
|
|
13
|
+
# | [T] (_NotFound[T] ifnone) { (Elem) -> boolish } -> (Elem | T)
|
|
14
|
+
# | [T] (_NotFound[T] ifnone) -> ::Enumerator[Elem, Elem | T]
|
|
15
|
+
# def detect: (?Proc ifnone) { (Elem) -> boolish } -> Elem?
|
|
16
|
+
# | (?Proc ifnone) -> ::Enumerator[Elem, Elem?]
|
|
17
|
+
#
|
|
18
|
+
# so `r = { a: 1 }.detect(-> { 0 }) { |k, v| v > 5 }` typed `[:a, 1]?`
|
|
19
|
+
# although it is `0` at runtime, and `r + 1` reported
|
|
20
|
+
# `call.possible-nil-receiver` on correct code. No core or stdlib class
|
|
21
|
+
# declares a `detect` of its own except `Array`, which rbs 4.1 and later
|
|
22
|
+
# alias to `Array#find`; `Hash`, `Range`, `Set`, `Struct`, `Enumerator`,
|
|
23
|
+
# `Enumerator::Lazy` (whose lazy method table leaves `detect` eager), `IO`,
|
|
24
|
+
# `Dir`, `ENV` and every other includer inherit this one, as does `Array`
|
|
25
|
+
# before rbs 4.1.
|
|
26
|
+
#
|
|
27
|
+
# Upstream's `?Proc` parameter also rejected a valid fallback that is not a
|
|
28
|
+
# `Proc` — `nil` (no fallback) or any object answering `call` — with
|
|
29
|
+
# `call.argument-type-mismatch`. Such an argument reaches the `_NotFound[T]`
|
|
30
|
+
# arms below, so `detect` checks its argument as `find` does.
|
|
31
|
+
#
|
|
32
|
+
# The overloads below are prepended to upstream's through `| ...` (rbs
|
|
33
|
+
# unshifts a continuation's overloads, so they are tried first). The last two
|
|
34
|
+
# are `find`'s ifnone arms. The first two restate them for a `Proc` argument:
|
|
35
|
+
# `OverloadSelector`'s strict pass skips an interface-typed parameter such as
|
|
36
|
+
# `_NotFound[T]`, so without them upstream's strictly typed `(?Proc ifnone)`
|
|
37
|
+
# arm would win a lambda and answer `Elem?` again. A block-only
|
|
38
|
+
# `detect { ... }` passes no argument, skips all four on arity, and keeps
|
|
39
|
+
# upstream's `Elem?`.
|
|
40
|
+
#
|
|
41
|
+
# The continuation's base is in rbs core on every supported release, so the
|
|
42
|
+
# file loads unconditionally. Once an upstream release aligns `detect` with
|
|
43
|
+
# `find`, gate this file off that release through
|
|
44
|
+
# `RbsLoader::RBS_LINE_CORE_OVERLAYS`, as `hash_rbs3.rbs` is.
|
|
45
|
+
module Enumerable[unchecked out Elem]
|
|
46
|
+
def detect: [T] (^() -> T ifnone) { (Elem) -> boolish } -> (Elem | T)
|
|
47
|
+
| [T] (^() -> T ifnone) -> ::Enumerator[Elem, Elem | T]
|
|
48
|
+
| [T] (_NotFound[T] ifnone) { (Elem) -> boolish } -> (Elem | T)
|
|
49
|
+
| [T] (_NotFound[T] ifnone) -> ::Enumerator[Elem, Elem | T]
|
|
50
|
+
| ...
|
|
51
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Rigor core overlay — supplemental core signatures.
|
|
2
|
+
#
|
|
3
|
+
# --- Enumerator::Lazy's redefined enumerable methods ---
|
|
4
|
+
#
|
|
5
|
+
# Issue #1121. `Enumerator::Lazy` is not merely an `Enumerator`: CRuby
|
|
6
|
+
# redefines most of `Enumerable` on it so that the call CHAINS another lazy
|
|
7
|
+
# enumerator instead of forcing the source (`enumerator.c`'s lazy method
|
|
8
|
+
# table: `collect` / `map`, `find_all` / `select` / `filter`, `reject`,
|
|
9
|
+
# `grep`, `grep_v`, `flat_map` / `collect_concat`, `take`, `take_while`,
|
|
10
|
+
# `drop`, `drop_while`, `uniq`, `zip`, `filter_map`). Upstream `ruby/rbs`
|
|
11
|
+
# declares none of them on `Enumerator::Lazy` — its whole body is `force`
|
|
12
|
+
# plus `compact` and `eager` — so dispatch walks up to `Enumerable` and
|
|
13
|
+
# adopts THAT class's eager return type:
|
|
14
|
+
#
|
|
15
|
+
# [1, 2, 3].lazy.map { |x| x * 2 } # Enumerable#map -> Array[U], not Lazy
|
|
16
|
+
#
|
|
17
|
+
# The receiver class is lost, and with it every terminal method only a lazy
|
|
18
|
+
# enumerator answers: `…map { … }.force` / `.eager` report
|
|
19
|
+
# `call.undefined-method` on correct code, on 124 `.lazy` sites in one
|
|
20
|
+
# survey corpus (gitlab services and graphql, rails actiontext, sidekiq,
|
|
21
|
+
# rubocop).
|
|
22
|
+
#
|
|
23
|
+
# The entries below mirror each upstream `Enumerable` declaration the lazy
|
|
24
|
+
# method replaces, with the return rewritten to `Enumerator::Lazy`. Two
|
|
25
|
+
# deliberate exclusions:
|
|
26
|
+
#
|
|
27
|
+
# * The single type argument spelling (`Enumerator::Lazy[U]`, not
|
|
28
|
+
# `[U, Return]`) mirrors upstream's own `Enumerable#lazy:
|
|
29
|
+
# () -> Enumerator::Lazy[Elem]` — the trailing `Return` parameter is
|
|
30
|
+
# defaulted (`void`), and Rigor binds the supplied prefix (see
|
|
31
|
+
# `docs/internal-spec/inference-engine.md` § generic dispatch).
|
|
32
|
+
# * The no-block arms of the block-taking methods are NOT carried over.
|
|
33
|
+
# CRuby RAISES for a blockless `lazy.map` / `select` / `reject` /
|
|
34
|
+
# `find_all` / `take_while` ("tried to call lazy map without a block"), so
|
|
35
|
+
# the inherited `Enumerable` arm is a wrong answer for a lazy receiver
|
|
36
|
+
# rather than a harmless fallback.
|
|
37
|
+
#
|
|
38
|
+
# `zip` is left to `Enumerable` for now: its lazy form yields
|
|
39
|
+
# `[elem, other_elem]` pairs through `_Each`, and the pair shape upstream
|
|
40
|
+
# declares for the eager form is not what the lazy chain hands downstream.
|
|
41
|
+
# `compact`, `eager` and `force` are upstream already and MUST NOT be
|
|
42
|
+
# duplicated here — a second declaration of a method `rbs` core already
|
|
43
|
+
# declares in this class is a `DuplicatedMethodDefinitionError`, which
|
|
44
|
+
# degrades the whole class to `Dynamic[top]`. (`eager` in particular is
|
|
45
|
+
# declared by rbs 4.x only; a `| ...` overload continuation that would
|
|
46
|
+
# declare it on the 3.10 line as well raises `InvalidOverloadMethodError`
|
|
47
|
+
# there, for the same whole-class cost, so `.eager` stays the 3.10 line's
|
|
48
|
+
# own gap.)
|
|
49
|
+
class Enumerator::Lazy[out E, out R = void]
|
|
50
|
+
def collect: [U] () { (E) -> U } -> Enumerator::Lazy[U]
|
|
51
|
+
|
|
52
|
+
def map: [U] () { (E) -> U } -> Enumerator::Lazy[U]
|
|
53
|
+
|
|
54
|
+
def find_all: () { (E) -> boolish } -> Enumerator::Lazy[E]
|
|
55
|
+
|
|
56
|
+
def select: () { (E) -> boolish } -> Enumerator::Lazy[E]
|
|
57
|
+
|
|
58
|
+
def filter: () { (E) -> boolish } -> Enumerator::Lazy[E]
|
|
59
|
+
|
|
60
|
+
def reject: () { (E) -> boolish } -> Enumerator::Lazy[E]
|
|
61
|
+
|
|
62
|
+
def filter_map: [U] () { (E) -> (nil | false | U) } -> Enumerator::Lazy[U]
|
|
63
|
+
|
|
64
|
+
def flat_map: [U] () { (E) -> (Array[U] | U) } -> Enumerator::Lazy[U]
|
|
65
|
+
|
|
66
|
+
def collect_concat: [U] () { (E) -> (Array[U] | U) } -> Enumerator::Lazy[U]
|
|
67
|
+
|
|
68
|
+
def take: (Integer n) -> Enumerator::Lazy[E]
|
|
69
|
+
|
|
70
|
+
def take_while: () { (E) -> boolish } -> Enumerator::Lazy[E]
|
|
71
|
+
|
|
72
|
+
def drop: (Integer n) -> Enumerator::Lazy[E]
|
|
73
|
+
|
|
74
|
+
def drop_while: () { (E) -> boolish } -> Enumerator::Lazy[E]
|
|
75
|
+
|
|
76
|
+
def uniq: () -> Enumerator::Lazy[E]
|
|
77
|
+
| () { (E item) -> untyped } -> Enumerator::Lazy[E]
|
|
78
|
+
|
|
79
|
+
def grep: (untyped pattern) -> Enumerator::Lazy[E]
|
|
80
|
+
| [U] (untyped pattern) { (E) -> U } -> Enumerator::Lazy[U]
|
|
81
|
+
|
|
82
|
+
def grep_v: (untyped pattern) -> Enumerator::Lazy[E]
|
|
83
|
+
| [U] (untyped pattern) { (E) -> U } -> Enumerator::Lazy[U]
|
|
84
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Rigor core overlay — supplemental core signatures, rbs 3.x line only.
|
|
2
|
+
#
|
|
3
|
+
# --- Hash#transform_keys / #transform_keys! with a replacements hash ---
|
|
4
|
+
#
|
|
5
|
+
# Ruby 3.0 added the replacements-hash argument
|
|
6
|
+
# (`h.transform_keys({ a: :z }) { |k| k.to_s }`), and rbs 4.0 declares it.
|
|
7
|
+
# Every rbs 3.x release declares only the blockless `Enumerator` form and
|
|
8
|
+
# the block form, both with no positional parameter:
|
|
9
|
+
#
|
|
10
|
+
# def transform_keys: () -> Enumerator[K, Hash[untyped, V]]
|
|
11
|
+
# | [A] () { (K) -> A } -> Hash[A, V]
|
|
12
|
+
# def transform_keys!: () -> Enumerator[K, self]
|
|
13
|
+
# | () { (K) -> K } -> self
|
|
14
|
+
#
|
|
15
|
+
# so under rbs 3.x a correct replacements call reports `call.wrong-arity`
|
|
16
|
+
# ("given 1, expected 0"), and `transform_keys!` answers `Enumerator`
|
|
17
|
+
# instead of the receiver.
|
|
18
|
+
#
|
|
19
|
+
# The overloads below are rbs 4.2's, prepended to 3.x's own through
|
|
20
|
+
# `| ...`. `RbsLoader::RBS_LINE_CORE_OVERLAYS` loads this file only on
|
|
21
|
+
# the rbs 3.x line: on 4.x the continuation would add a second copy of
|
|
22
|
+
# the overloads upstream already declares.
|
|
23
|
+
#
|
|
24
|
+
# Two spellings differ from rbs 4.2 so that every name resolves on every
|
|
25
|
+
# rbs 3.x release the gemspec admits:
|
|
26
|
+
#
|
|
27
|
+
# * `hash[K, V]` is written out as `Hash[K, V] | _ToHash[K, V]`: the
|
|
28
|
+
# `hash` alias arrived in rbs 3.3, `_ToHash` is in 3.0.
|
|
29
|
+
# * `hash[_Key, K2]`'s key is `untyped`: `Hash::_Key` arrived in rbs 3.4.
|
|
30
|
+
# It constrains only `hash` / `eql?`, which every object answers.
|
|
31
|
+
#
|
|
32
|
+
# The non-bang return type is answered ahead of either declaration by
|
|
33
|
+
# `MethodDispatcher::HashTransformKeysFolding`.
|
|
34
|
+
class Hash[unchecked out K, unchecked out V]
|
|
35
|
+
def transform_keys: [K2] (Hash[untyped, K2] | _ToHash[untyped, K2] replacements) -> Hash[K | K2, V]
|
|
36
|
+
| [K2] (Hash[untyped, K2] | _ToHash[untyped, K2] replacements) { (K old_key) -> K2 } -> Hash[K2, V]
|
|
37
|
+
| ...
|
|
38
|
+
|
|
39
|
+
def transform_keys!: (Hash[K, K] | _ToHash[K, K] replacements) ?{ (K key) -> K } -> self
|
|
40
|
+
| ...
|
|
41
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Rigor core overlay — supplemental core signatures.
|
|
2
|
+
#
|
|
3
|
+
# --- Process.last_status ---
|
|
4
|
+
#
|
|
5
|
+
# CRuby's `process.c` defines `Process.last_status` as a singleton method
|
|
6
|
+
# answering the calling thread's last child status — the object `$?` reads:
|
|
7
|
+
#
|
|
8
|
+
# rb_define_singleton_method(rb_mProcess, "last_status", proc_s_last_status, 0);
|
|
9
|
+
#
|
|
10
|
+
# It is `nil` until the thread has waited on a child process. Upstream
|
|
11
|
+
# `ruby/rbs` names `::last_status` in `Process`'s doc comment but has never
|
|
12
|
+
# declared it (no commit to `core/process.rbs` through 4.2 adds a `def`), so
|
|
13
|
+
#
|
|
14
|
+
# system("true")
|
|
15
|
+
# status = Process.last_status
|
|
16
|
+
#
|
|
17
|
+
# reported `call.undefined-method` on correct code. The return mirrors rbs
|
|
18
|
+
# core's own `$?: Process::Status?`.
|
|
19
|
+
#
|
|
20
|
+
# Declared on a module `Process` extends, not with `def self.last_status`.
|
|
21
|
+
# A second direct `def` of the method is a `DuplicatedMethodDefinitionError`
|
|
22
|
+
# that takes `Process`'s whole singleton surface down to `Dynamic[top]`, and
|
|
23
|
+
# this method is one a project is likely to have declared already: a
|
|
24
|
+
# `sig/` written for Steep, which reads the same rbs gap, or a later rbs
|
|
25
|
+
# release that fills it. A direct `def` from either source overrides a
|
|
26
|
+
# method `Process` gets through `extend`, on rbs 3.x and 4.x alike, so the
|
|
27
|
+
# overlay stands down instead of colliding.
|
|
28
|
+
#
|
|
29
|
+
# Unconditional: `Process` and `Process::Status` are rbs core on every
|
|
30
|
+
# supported release. `MethodDispatcher::ProcessFolding` refines the answer
|
|
31
|
+
# past a subprocess call, where `$?` is known to be set.
|
|
32
|
+
module RigorCoreOverlay
|
|
33
|
+
module ProcessLastStatus
|
|
34
|
+
def last_status: () -> Process::Status?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module Process
|
|
39
|
+
extend RigorCoreOverlay::ProcessLastStatus
|
|
40
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Rigor core overlay — supplemental core signatures.
|
|
2
|
+
#
|
|
3
|
+
# --- StringIO includes Enumerable[String] ---
|
|
4
|
+
#
|
|
5
|
+
# CRuby's `ext/stringio/stringio.c` makes `StringIO` Enumerable:
|
|
6
|
+
#
|
|
7
|
+
# rb_include_module(StringIO, rb_mEnumerable);
|
|
8
|
+
#
|
|
9
|
+
# and `StringIO#each` yields the stream's lines, so a `StringIO` is an
|
|
10
|
+
# `Enumerable[String]` exactly as rbs core declares `IO` to be. Upstream
|
|
11
|
+
# `ruby/rbs` declares `class StringIO` with no `include` on every release the
|
|
12
|
+
# gemspec supports, so `StringIO.new(s).detect { ... }`, `.map`,
|
|
13
|
+
# `.each_with_index` and the rest of Enumerable reported
|
|
14
|
+
# `call.undefined-method` on correct code, and a block-taking `.select` fell
|
|
15
|
+
# through to `Kernel#select` (the IO multiplexer) and reported
|
|
16
|
+
# `call.wrong-arity`.
|
|
17
|
+
#
|
|
18
|
+
# The mixin sits behind the class in the ancestry, so every method StringIO
|
|
19
|
+
# declares itself (`each`, `each_line`, `each_char`, `readlines`, ...) keeps
|
|
20
|
+
# upstream's signature. A duplicate include, should upstream add one, builds
|
|
21
|
+
# on both rbs lines.
|
|
22
|
+
#
|
|
23
|
+
# Unconditional — NOT in `RbsLoader::LIBRARY_SUPPLEMENT_CORE_OVERLAYS`,
|
|
24
|
+
# because every environment Rigor builds already declares `StringIO`: in rbs
|
|
25
|
+
# `core/string_io.rbs` before 3.7, and from 3.7 on because
|
|
26
|
+
# `RBS::EnvironmentLoader#load` adds the `stringio` library itself whenever
|
|
27
|
+
# it loads core ("For migrating stringio to stdlib"). Gating on `stringio`
|
|
28
|
+
# would drop the mixin on rbs < 3.7, which has no such library, and from any
|
|
29
|
+
# environment that does not name it. The premise is pinned in
|
|
30
|
+
# `spec/rigor/environment/bundled_rbs_definition_build_spec.rb`.
|
|
31
|
+
class StringIO
|
|
32
|
+
include Enumerable[String]
|
|
33
|
+
end
|
data/data/effects/core.yml
CHANGED
|
@@ -173,7 +173,7 @@ classes:
|
|
|
173
173
|
instance_variable_set:
|
|
174
174
|
effects: []
|
|
175
175
|
mutates: receiver
|
|
176
|
-
why: "writes another object's state; the label follows the receiver's ownership (
|
|
176
|
+
why: "writes another object's state; the label follows the receiver's ownership (effect-summaries.md § Ownership)"
|
|
177
177
|
instance_variable_get: { effects: [], why: "reads an ivar — object state, not global state" }
|
|
178
178
|
hash: { effects: [], why: "per-process SipHash salt makes it irreproducible (`NON_REPRODUCIBLE_SELECTORS`), but irreproducibility is a fold hazard, not an observable effect" }
|
|
179
179
|
object_id: { effects: [], why: "per-process identity — a fold hazard (`NON_REPRODUCIBLE_SELECTORS`), not an effect" }
|
|
@@ -235,12 +235,12 @@ classes:
|
|
|
235
235
|
Hash:
|
|
236
236
|
posture: value
|
|
237
237
|
mutators: hash
|
|
238
|
-
why: "a value class; mutation is ownership-classified from `
|
|
238
|
+
why: "a value class; mutation is ownership-classified from `MutationClassifier::HASH_MUTATORS`, the union of the widening's `HASH_MUTATORS` and `HashLookupMutation::MUTATORS` plus `rehash`"
|
|
239
239
|
|
|
240
240
|
String:
|
|
241
241
|
posture: value
|
|
242
242
|
mutators: string
|
|
243
|
-
why: "a value class; mutation is ownership-classified from `
|
|
243
|
+
why: "a value class; mutation is ownership-classified from `StringMutation::MUTATORS`"
|
|
244
244
|
|
|
245
245
|
Symbol: { posture: value, why: "immutable value class" }
|
|
246
246
|
Integer: { posture: value, why: "immutable value class" }
|
|
@@ -68,6 +68,8 @@ class Object
|
|
|
68
68
|
|
|
69
69
|
# `core_ext/object/inclusion` — `x.in?([a, b])` / `x.in?(1..10)`.
|
|
70
70
|
def in?: (untyped) -> bool
|
|
71
|
+
# `in?(another_object) ? self : nil` — `params[:kind].presence_in(%w[a b])`.
|
|
72
|
+
def presence_in: (untyped) -> self?
|
|
71
73
|
|
|
72
74
|
# Issue #673 — `core_ext/object/to_query`, `core_ext/object/duplicable` and
|
|
73
75
|
# `core_ext/object/instance_variables` all hang off `Object`, so every
|
|
@@ -93,6 +95,50 @@ class Object
|
|
|
93
95
|
# `instance_variable_names`.
|
|
94
96
|
def instance_values: () -> Hash[String, untyped]
|
|
95
97
|
def instance_variable_names: () -> Array[String]
|
|
98
|
+
|
|
99
|
+
# `core_ext/object/deep_dup` — `duplicable? ? dup : self`, plus `Array`
|
|
100
|
+
# (`map(&:deep_dup)`), `Hash` and `Module` overrides. `self` over-claims on
|
|
101
|
+
# an `Array` subclass, whose override answers a plain `Array`. Declared on
|
|
102
|
+
# `Object` and not only on `Hash`, where it sat alone: `[[1], [2]].deep_dup`
|
|
103
|
+
# is the common shape.
|
|
104
|
+
def deep_dup: () -> self
|
|
105
|
+
|
|
106
|
+
# `core_ext/object/with` (ActiveSupport 7.1+) — sets each keyword through
|
|
107
|
+
# its public writer, yields `self`, and restores the old values in an
|
|
108
|
+
# `ensure`; the answer is the block's. There is no blockless form: `yield` without a block raises.
|
|
109
|
+
# ActiveSupport `undef_method`s it on `NilClass`, `TrueClass`, `FalseClass`,
|
|
110
|
+
# `Integer`, `Float` and `Symbol`, which RBS cannot express, so `1.with(a: 1)`
|
|
111
|
+
# resolves here where Ruby raises — a missed error, not a false one.
|
|
112
|
+
# `Data#with` (core) is a subclass override and keeps its own `-> self`.
|
|
113
|
+
def with: [T] (**untyped attributes) { (self) -> T } -> T
|
|
114
|
+
|
|
115
|
+
# `core_ext/object/with_options` — without a block, the
|
|
116
|
+
# `ActiveSupport::OptionMerger` proxy (not declared here); with one, the
|
|
117
|
+
# block's value. A zero-arity block is `instance_eval`ed on the proxy, the
|
|
118
|
+
# `with_options dependent: :destroy do has_many … end` model-body shape.
|
|
119
|
+
def with_options: (untyped options) -> untyped
|
|
120
|
+
| [T] (untyped options) { (?) -> T } -> T
|
|
121
|
+
|
|
122
|
+
# `core_ext/string/output_safety` — `false` on `Object`, `true` on
|
|
123
|
+
# `Numeric`, the buffer's own state on `ActiveSupport::SafeBuffer`. `bool`
|
|
124
|
+
# for the reason `duplicable?` is: `-> false` on the universal receiver makes
|
|
125
|
+
# every `if x.html_safe?` an always-falsy branch on the subclasses that answer
|
|
126
|
+
# true.
|
|
127
|
+
def html_safe?: () -> bool
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------
|
|
131
|
+
# Kernel — `core_ext/kernel/singleton_class`
|
|
132
|
+
# ---------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
# `singleton_class.class_eval(*args, &block)` on any object. Declared on
|
|
135
|
+
# `Kernel`, where ActiveSupport defines it; a `Module` receiver still answers
|
|
136
|
+
# core `Module#class_eval`, which is its own alias of `module_eval`. The two
|
|
137
|
+
# overloads mirror that `module_eval`: a string, or a block given the
|
|
138
|
+
# singleton class.
|
|
139
|
+
module Kernel
|
|
140
|
+
def class_eval: (string code, ?string? filename, ?int lineno) -> untyped
|
|
141
|
+
| [U] () { (untyped singleton_class) -> U } -> U
|
|
96
142
|
end
|
|
97
143
|
|
|
98
144
|
# `nil.blank?` / `nil.present?` / `nil.try` are the most frequent
|
|
@@ -149,6 +195,10 @@ class String
|
|
|
149
195
|
def titlecase: () -> String
|
|
150
196
|
def dasherize: () -> String
|
|
151
197
|
def upcase_first: () -> String
|
|
198
|
+
def downcase_first: () -> String # ActiveSupport 7.1+
|
|
199
|
+
|
|
200
|
+
# `core_ext/string/behavior` — the `acts_like?(:string)` duck-type hook.
|
|
201
|
+
def acts_like_string?: () -> true
|
|
152
202
|
|
|
153
203
|
# `core_ext/string/filters`
|
|
154
204
|
def squish: () -> String
|
|
@@ -182,6 +232,12 @@ class String
|
|
|
182
232
|
def to_datetime: () -> DateTime
|
|
183
233
|
def to_hours: () -> Float
|
|
184
234
|
|
|
235
|
+
# `core_ext/string/zones` — `Time.find_zone!(zone).parse(self)`, or
|
|
236
|
+
# `to_time` when no zone is set. `untyped` like the `Time` / `Date` rows:
|
|
237
|
+
# the parse answers an `ActiveSupport::TimeWithZone`, or `nil` on a string
|
|
238
|
+
# with no date parts.
|
|
239
|
+
def in_time_zone: (?untyped zone) -> untyped
|
|
240
|
+
|
|
185
241
|
# `core_ext/string/access`
|
|
186
242
|
def at: (Integer | Range[Integer] | Regexp position) -> String?
|
|
187
243
|
def from: (Integer position) -> String
|
|
@@ -194,6 +250,7 @@ class String
|
|
|
194
250
|
|
|
195
251
|
# `core_ext/string/multibyte`
|
|
196
252
|
def mb_chars: () -> untyped
|
|
253
|
+
def is_utf8?: () -> bool
|
|
197
254
|
|
|
198
255
|
# `core_ext/string/inquiry`
|
|
199
256
|
def inquiry: () -> untyped # ActiveSupport::StringInquirer
|
|
@@ -202,6 +259,17 @@ class String
|
|
|
202
259
|
def exclude?: (String) -> bool
|
|
203
260
|
end
|
|
204
261
|
|
|
262
|
+
# ---------------------------------------------------------------
|
|
263
|
+
# Symbol — `core_ext/symbol/starts_ends_with`
|
|
264
|
+
# ---------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
# `alias_method`s of core `start_with?` / `end_with?`, declared with the core
|
|
267
|
+
# parameter lists.
|
|
268
|
+
class Symbol
|
|
269
|
+
def starts_with?: (*Regexp | string prefixes) -> bool
|
|
270
|
+
def ends_with?: (*string suffixes) -> bool
|
|
271
|
+
end
|
|
272
|
+
|
|
205
273
|
# ---------------------------------------------------------------
|
|
206
274
|
# Integer — `core_ext/integer/*`
|
|
207
275
|
# ---------------------------------------------------------------
|
|
@@ -762,6 +830,8 @@ class Array[unchecked out Elem]
|
|
|
762
830
|
def fourth: () -> Elem?
|
|
763
831
|
def fifth: () -> Elem?
|
|
764
832
|
def forty_two: () -> Elem?
|
|
833
|
+
def second_to_last: () -> Elem?
|
|
834
|
+
def third_to_last: () -> Elem?
|
|
765
835
|
|
|
766
836
|
# `core_ext/array/grouping`
|
|
767
837
|
def in_groups_of: (Integer number, ?untyped fill_with) -> Array[Array[Elem]]
|
|
@@ -783,6 +853,9 @@ class Array[unchecked out Elem]
|
|
|
783
853
|
# `core_ext/array/extract`
|
|
784
854
|
def extract!: () { (Elem) -> bool } -> Array[Elem]
|
|
785
855
|
|
|
856
|
+
# `core_ext/array/extract_options`
|
|
857
|
+
def extract_options!: () -> Hash[untyped, untyped]
|
|
858
|
+
|
|
786
859
|
# `core_ext/object/blank` / `core_ext/enumerable` — both
|
|
787
860
|
# ActiveSupport additions, shipped on Array specifically.
|
|
788
861
|
def compact_blank: () -> Array[Elem]
|
|
@@ -810,6 +883,13 @@ module Enumerable[unchecked out Elem]
|
|
|
810
883
|
def minimum: (Symbol | String) -> untyped
|
|
811
884
|
def sole: () -> Elem
|
|
812
885
|
def compact_blank: () -> Array[Elem]
|
|
886
|
+
def many?: () -> bool
|
|
887
|
+
| () { (Elem) -> boolish } -> bool
|
|
888
|
+
# `key` and `series` are untyped on purpose: the default `filter: true`
|
|
889
|
+
# path is `group_by(&key).values_at(*series)`, so a Proc key or a Set /
|
|
890
|
+
# Range series is correct Rails code. `filter:` is ActiveSupport 8.x;
|
|
891
|
+
# passing it on 7.x raises, which this unversioned row does not catch.
|
|
892
|
+
def in_order_of: (untyped key, untyped series, ?filter: boolish) -> Array[Elem]
|
|
813
893
|
end
|
|
814
894
|
|
|
815
895
|
# ---------------------------------------------------------------
|
|
@@ -831,6 +911,9 @@ class Hash[unchecked out K, unchecked out V]
|
|
|
831
911
|
def deep_transform_keys!: () { (K) -> K } -> self
|
|
832
912
|
def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
|
|
833
913
|
def deep_transform_values!: () { (V) -> untyped } -> self
|
|
914
|
+
# `alias_method`s of `symbolize_keys` / `symbolize_keys!`.
|
|
915
|
+
def to_options: () -> Hash[Symbol, V]
|
|
916
|
+
def to_options!: () -> self
|
|
834
917
|
|
|
835
918
|
# `core_ext/hash/deep_dup`
|
|
836
919
|
def deep_dup: () -> Hash[K, V]
|
|
@@ -840,6 +923,9 @@ class Hash[unchecked out K, unchecked out V]
|
|
|
840
923
|
| (Hash[K, V]) { (K, V, V) -> V } -> Hash[K, V]
|
|
841
924
|
def deep_merge!: (Hash[K, V]) -> self
|
|
842
925
|
| (Hash[K, V]) { (K, V, V) -> V } -> self
|
|
926
|
+
# `other.is_a?(Hash)` — the hook `ActiveSupport::DeepMergeable` asks before
|
|
927
|
+
# recursing into a value. `:nodoc:`, but public.
|
|
928
|
+
def deep_merge?: (untyped other) -> bool
|
|
843
929
|
|
|
844
930
|
# `core_ext/hash/except` — `Hash#except` is in core RBS as of
|
|
845
931
|
# Ruby 3.0+; `except!` is ActiveSupport-only. ActiveSupport
|
|
@@ -853,8 +939,10 @@ class Hash[unchecked out K, unchecked out V]
|
|
|
853
939
|
def to_param: (?String namespace) -> String
|
|
854
940
|
def to_xml: (**untyped) -> String
|
|
855
941
|
|
|
856
|
-
# `core_ext/hash/indifferent_access`
|
|
942
|
+
# `core_ext/hash/indifferent_access` — `nested_under_indifferent_access`
|
|
943
|
+
# is an `alias` of `with_indifferent_access`.
|
|
857
944
|
def with_indifferent_access: () -> untyped
|
|
945
|
+
def nested_under_indifferent_access: () -> untyped
|
|
858
946
|
|
|
859
947
|
# `core_ext/hash/conversions`
|
|
860
948
|
def self.from_xml: (String, ?Symbol disallowed_types) -> Hash[String, untyped]
|
|
@@ -864,14 +952,49 @@ class Hash[unchecked out K, unchecked out V]
|
|
|
864
952
|
def compact_blank: () -> Hash[K, V]
|
|
865
953
|
def compact_blank!: () -> self
|
|
866
954
|
|
|
867
|
-
# `core_ext/
|
|
868
|
-
def
|
|
955
|
+
# `core_ext/array/extract_options`
|
|
956
|
+
def extractable_options?: () -> bool
|
|
957
|
+
|
|
958
|
+
# `core_ext/hash/reverse_merge` — `other_hash.merge(self)`, so the result
|
|
959
|
+
# carries the argument's keys and values as well as the receiver's, the
|
|
960
|
+
# same shape as core `Hash#merge`. `-> Hash[K, V]` typed
|
|
961
|
+
# `opts.reverse_merge(size: 25, velocity: 10)` as `Hash[:size, 1]`.
|
|
962
|
+
def reverse_merge: [A, B] (Hash[A, B] other_hash) -> Hash[A | K, B | V]
|
|
869
963
|
def reverse_merge!: (Hash[K, V]) -> self
|
|
964
|
+
# `alias_method`s: `with_defaults` of `reverse_merge`, `with_defaults!`
|
|
965
|
+
# and `reverse_update` of `reverse_merge!`.
|
|
966
|
+
def with_defaults: [A, B] (Hash[A, B] other_hash) -> Hash[A | K, B | V]
|
|
967
|
+
def with_defaults!: (Hash[K, V]) -> self
|
|
968
|
+
def reverse_update: (Hash[K, V]) -> self
|
|
870
969
|
|
|
871
970
|
# `core_ext/hash/slice` — `Hash#except` is in core RBS (Ruby 3.0+);
|
|
872
971
|
# `Hash#slice` is in core RBS (Ruby 2.5+); the bang variants are
|
|
873
|
-
# ActiveSupport-only.
|
|
972
|
+
# ActiveSupport-only. `extract!` deletes the named keys and answers them
|
|
973
|
+
# as a new hash; a key the receiver lacks is skipped, not `nil`-filled.
|
|
874
974
|
def slice!: (*K) -> Hash[K, V]
|
|
975
|
+
def extract!: (*K) -> Hash[K, V]
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
# ---------------------------------------------------------------
|
|
979
|
+
# Range — `core_ext/range/*`
|
|
980
|
+
# ---------------------------------------------------------------
|
|
981
|
+
|
|
982
|
+
# `[out E]` matches core `range.rbs`. A reopening whose parameters differ in
|
|
983
|
+
# count or variance (`class Range`, `class Range[E]`) raises
|
|
984
|
+
# `RBS::GenericParameterMismatchError` and collapses `Range`.
|
|
985
|
+
class Range[out E]
|
|
986
|
+
# `core_ext/range/overlap` — an `alias` of `overlap?`, which rbs declares
|
|
987
|
+
# since Ruby 3.3 (ActiveSupport defines the method itself only on an older
|
|
988
|
+
# Ruby). `overlap?` is therefore NOT redeclared here: a second declaration
|
|
989
|
+
# is a `DuplicatedMethodDefinitionError` that collapses the whole class.
|
|
990
|
+
def overlaps?: (Range[untyped]) -> bool
|
|
991
|
+
|
|
992
|
+
# `core_ext/range/conversions` — `ActiveSupport::RangeWithFormat`, prepended
|
|
993
|
+
# onto `Range`. An unknown format falls back to `to_s`; `:db` answers `nil`
|
|
994
|
+
# for a beginless-and-endless range, which the declared `String` does not
|
|
995
|
+
# model — `(nil..nil).to_fs(:db)` is not a shape correct code writes.
|
|
996
|
+
def to_fs: (?Symbol format) -> String
|
|
997
|
+
def to_formatted_s: (?Symbol format) -> String
|
|
875
998
|
end
|
|
876
999
|
|
|
877
1000
|
# ---------------------------------------------------------------
|
|
@@ -77,6 +77,32 @@ unless x.is_a?(Integer)
|
|
|
77
77
|
end
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
A guard can also name a class the variable's type says it cannot
|
|
81
|
+
be. Inside such a branch the variable reads `bot`, so no call on
|
|
82
|
+
it is checked. That matters when the type is one Rigor inferred
|
|
83
|
+
or read from a signature: `$stdout` is typed `IO`, and a test runs
|
|
84
|
+
the same code with a `StringIO`, which is not an `IO` subclass, so
|
|
85
|
+
the guarded call below is correct code and stays quiet. A guard
|
|
86
|
+
on `$stdout`, `STDOUT` or another global or constant narrows the
|
|
87
|
+
same way:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
require "stringio"
|
|
91
|
+
|
|
92
|
+
def captured_output
|
|
93
|
+
out = STDOUT
|
|
94
|
+
if out.is_a?(StringIO)
|
|
95
|
+
assert_type("bot", out)
|
|
96
|
+
out.string
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The rest of the branch is still checked, and its `bot` variable
|
|
102
|
+
never reaches the code after it. A `case` drops such a branch
|
|
103
|
+
from its value (see below); keeping it is tracked in
|
|
104
|
+
[#1465](https://github.com/rigortype/rigor/issues/1465).
|
|
105
|
+
|
|
80
106
|
## Equality with literal values
|
|
81
107
|
|
|
82
108
|
Rigor narrows `==` and `!=` against trusted literal values:
|
|
@@ -117,17 +143,78 @@ The result type unions the per-branch results. When the input
|
|
|
117
143
|
is a finite literal union, Rigor proves the `else` branch is
|
|
118
144
|
unreachable when every member is matched.
|
|
119
145
|
|
|
120
|
-
The same narrowing also works in reverse: when
|
|
121
|
-
|
|
122
|
-
|
|
146
|
+
The same narrowing also works in reverse: when an earlier clause
|
|
147
|
+
already covered the subject, or a `when <Class>` clause is
|
|
148
|
+
disjoint from the subject's type, the clause is dead — Rigor emits
|
|
123
149
|
[`flow.unreachable-clause`](08-understanding-errors.md) so you
|
|
124
150
|
can delete it. (It ships at `:info` under the default profile.)
|
|
125
151
|
|
|
152
|
+
A disjoint `when <Class>` is reported only when the subject's
|
|
153
|
+
type is what the code literally shows: a literal (`1`, `"s"`,
|
|
154
|
+
`:a`), an array or hash literal, a class object, or `nil` /
|
|
155
|
+
`true` / `false`. When the subject's type is one Rigor inferred
|
|
156
|
+
or read from a signature, the `when` is evidence that it can hold
|
|
157
|
+
something else, as in the `is_a?` example above, so the clause is
|
|
158
|
+
not reported. It is still read as `bot`, and the `case` value
|
|
159
|
+
drops it:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
require "stringio"
|
|
163
|
+
|
|
164
|
+
io = STDOUT
|
|
165
|
+
assert_type(":io", (case io when StringIO then :string_io else :io end))
|
|
166
|
+
|
|
167
|
+
count = 3
|
|
168
|
+
assert_type(":number", (case count when String then :text else :number end))
|
|
169
|
+
|
|
170
|
+
case io
|
|
171
|
+
when StringIO then puts io.string # not reported
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
case count
|
|
175
|
+
when String then puts count # flow.unreachable-clause
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Both values leave the `when` branch out. Of the two `case`
|
|
180
|
+
statements after them, only the second reports
|
|
181
|
+
`flow.unreachable-clause`. The check reads a `case` the code runs
|
|
182
|
+
as a statement or assigns to a variable; one passed straight to a
|
|
183
|
+
method, like the two `assert_type` lines, is not checked.
|
|
184
|
+
|
|
126
185
|
`case x; in pattern` (one-line pattern matching) narrows the
|
|
127
186
|
same way for the patterns Rigor understands — class checks,
|
|
128
187
|
literal equality, array / hash structural patterns. The
|
|
129
188
|
clause-reachability check extends to bare-class `in` patterns
|
|
130
|
-
(`in String` / `in MyClass => x`) too
|
|
189
|
+
(`in String` / `in MyClass => x`) too, under the same literal
|
|
190
|
+
rule.
|
|
191
|
+
|
|
192
|
+
## `respond_to?`
|
|
193
|
+
|
|
194
|
+
A truthy `respond_to?(:name)` with a literal symbol removes `nil`
|
|
195
|
+
from the receiver (unless `nil` itself responds to `name`) and the
|
|
196
|
+
members whose class Rigor knows lacks `name`. When no member could
|
|
197
|
+
respond, the receiver reads `Dynamic[top]` if its type is one
|
|
198
|
+
Rigor inferred or read from a signature, so the guarded call is
|
|
199
|
+
not an error. It reads `bot` if it is a literal, an array or hash
|
|
200
|
+
literal, a class object, or `nil` / `true` / `false`, so the
|
|
201
|
+
branch goes unchecked. For a literal value that is exact: it
|
|
202
|
+
never gains the method. A class object can gain one from a gem
|
|
203
|
+
Rigor has no signature for (`Time.respond_to?(:zone)` under
|
|
204
|
+
ActiveSupport), and its branch is then not checked either:
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
require "stringio"
|
|
208
|
+
|
|
209
|
+
value = [1, "one"].sample
|
|
210
|
+
assert_type('"one"', value) if value.respond_to?(:upcase)
|
|
211
|
+
|
|
212
|
+
io = STDOUT
|
|
213
|
+
assert_type("Dynamic[top]", io) if io.respond_to?(:string)
|
|
214
|
+
|
|
215
|
+
count = 3
|
|
216
|
+
assert_type("bot", count) if count.respond_to?(:upcase)
|
|
217
|
+
```
|
|
131
218
|
|
|
132
219
|
## Boolean composition
|
|
133
220
|
|
|
@@ -343,12 +430,10 @@ not chase mutation).
|
|
|
343
430
|
A few forms you might expect that Rigor does **not** narrow
|
|
344
431
|
today:
|
|
345
432
|
|
|
346
|
-
- `respond_to?(:method_name)`
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
structural "this object responds to that method" capability
|
|
351
|
-
fact you could dispatch against.
|
|
433
|
+
- `respond_to?(:method_name)` narrows by removing members (see
|
|
434
|
+
[`respond_to?`](#respond_to) below), but it does **not** yet
|
|
435
|
+
expose a structural "this object responds to that method"
|
|
436
|
+
capability fact you could dispatch against.
|
|
352
437
|
- `frozen?` and other mutation guards — Rigor does not track
|
|
353
438
|
mutability as a narrowing fact yet.
|
|
354
439
|
- Open-ended class-comparison via `===` against arbitrary
|