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
|
@@ -184,23 +184,25 @@ final = { **defaults, **overrides }
|
|
|
184
184
|
## Pattern matching destructuring
|
|
185
185
|
|
|
186
186
|
`case x in [a, b, c]` narrows `a` / `b` / `c` per-position
|
|
187
|
-
exactly like multiple-assignment:
|
|
187
|
+
exactly like multiple-assignment: the pattern binds against the
|
|
188
|
+
subject's type, so a tuple subject hands each slot its element and
|
|
189
|
+
an `Array[T]` subject hands each slot `T`.
|
|
188
190
|
|
|
189
191
|
```ruby
|
|
190
192
|
case [10, 20, 30]
|
|
191
193
|
in [first, _, third]
|
|
192
|
-
assert_type("
|
|
193
|
-
assert_type("
|
|
194
|
+
assert_type("10", first)
|
|
195
|
+
assert_type("30", third)
|
|
194
196
|
end
|
|
195
197
|
```
|
|
196
198
|
|
|
197
|
-
Hash patterns
|
|
199
|
+
Hash patterns read the subject's own `deconstruct_keys` projection:
|
|
198
200
|
|
|
199
201
|
```ruby
|
|
200
202
|
case { name: "Alice", age: 30 }
|
|
201
203
|
in { name:, age: }
|
|
202
|
-
assert_type("
|
|
203
|
-
assert_type("
|
|
204
|
+
assert_type("\"Alice\"", name)
|
|
205
|
+
assert_type("30", age)
|
|
204
206
|
end
|
|
205
207
|
```
|
|
206
208
|
|
|
@@ -467,17 +467,22 @@ Notes:
|
|
|
467
467
|
falls back to no inline-RBS contribution and analysis
|
|
468
468
|
continues.
|
|
469
469
|
- When a method is declared **both** in `sig/` and by an
|
|
470
|
-
inline annotation,
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
470
|
+
inline annotation, Rigor compares the two and one of them
|
|
471
|
+
binds. If one refines the other — `Symbol` in `sig/`,
|
|
472
|
+
`:asc | :desc` inline — the more precise one binds and
|
|
473
|
+
nothing is reported; `untyped` and `void` are consistent
|
|
474
|
+
with anything. If they contradict — a `String` parameter
|
|
475
|
+
against an `Integer` one — the `.rbs` binds and the run
|
|
476
|
+
reports `rbs.contradicting-signature`, an error, at the
|
|
477
|
+
`sig/` line: usually a stale generated signature. If Rigor
|
|
478
|
+
cannot tell, the `.rbs` binds and the dropped inline
|
|
479
|
+
signature is reported as a `source-rbs-annotation-not-honoured`
|
|
480
|
+
`:info` naming both files. Only the overlapping member is
|
|
481
|
+
affected; every other annotation in the file still binds,
|
|
482
|
+
and the class keeps its method surface. rbs and Steep merge
|
|
483
|
+
the two sources without ranking them, so left to collide
|
|
484
|
+
they fail the class's definition build and every call on it
|
|
485
|
+
— real methods and typos alike — reads `Dynamic[top]`.
|
|
481
486
|
|
|
482
487
|
Full plugin documentation, configuration options (including
|
|
483
488
|
the `require_magic_comment: false` host-context override the
|
data/docs/handbook/10-sorbet.md
CHANGED
|
@@ -277,8 +277,9 @@ warnings until the engine's case narrowing improves.
|
|
|
277
277
|
|
|
278
278
|
## Tier ordering — what wins on conflict
|
|
279
279
|
|
|
280
|
-
When a method has both a Sorbet `sig` and an RBS sig,
|
|
281
|
-
|
|
280
|
+
When a method has both a Sorbet `sig` and an RBS sig, the
|
|
281
|
+
Sorbet `sig` types the call site. Sorbet sigs sit at Rigor's
|
|
282
|
+
plugin tier, which answers before RBS dispatch:
|
|
282
283
|
|
|
283
284
|
1. **Precision tiers** — constant fold, shape dispatch,
|
|
284
285
|
block fold, etc.
|
|
@@ -289,14 +290,12 @@ wins. Sorbet sigs sit at Rigor's plugin tier:
|
|
|
289
290
|
4. **Dependency-source inference** (ADR-10's opt-in walker).
|
|
290
291
|
5. **User-class fallback** (`Object` / `Class` ancestors).
|
|
291
292
|
|
|
292
|
-
|
|
293
|
-
[
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
(Sorbet wins) would let third-party-DSL annotations
|
|
299
|
-
override authored RBS, which inverts the trust model.
|
|
293
|
+
A plugin's return type replaces the RBS return with no
|
|
294
|
+
diagnostic ([ADR-2](../adr/2-extension-api.md) § "Amendment
|
|
295
|
+
2026-09-26"), so where the two sigs disagree, callers see the
|
|
296
|
+
Sorbet return. The RBS still binds the method's body, which
|
|
297
|
+
is checked against the declared return. Keep the two
|
|
298
|
+
signatures in agreement; remove the one you no longer mean.
|
|
300
299
|
|
|
301
300
|
## Migration patterns
|
|
302
301
|
|
data/docs/handbook/11-sig-gen.md
CHANGED
|
@@ -9,7 +9,7 @@ tooltips, downstream consumers reading your gem's `sig/` —
|
|
|
9
9
|
sees what Rigor sees.
|
|
10
10
|
|
|
11
11
|
This chapter is a walkthrough of the command's UX, the
|
|
12
|
-
classification model, the
|
|
12
|
+
classification model, the output modes, and the
|
|
13
13
|
`--params` policy trade-off that comes straight out of
|
|
14
14
|
[ADR-5](../adr/5-robustness-principle.md)'s asymmetric
|
|
15
15
|
"strict on returns, lenient on parameters" rule.
|
|
@@ -67,30 +67,70 @@ By default the command writes nothing — it prints the
|
|
|
67
67
|
proposal so you can review it. Pass `--write` to apply the
|
|
68
68
|
proposal to `sig/`.
|
|
69
69
|
|
|
70
|
-
## The
|
|
70
|
+
## The output modes
|
|
71
71
|
|
|
72
72
|
| Mode | Behaviour |
|
|
73
73
|
| --- | --- |
|
|
74
74
|
| `--print` (default) | Print RBS to stdout, grouped by source file + class declaration. |
|
|
75
75
|
| `--diff` | Show a unified-style diff comparing the existing-declared spelling (if any) against the inferred spelling. Read-only. |
|
|
76
76
|
| `--write` | Apply the proposal to `sig/<path>.rbs`. Creates files, inserts new methods into existing class declarations, appends new class blocks to files that don't declare them yet. |
|
|
77
|
+
| `--check` | Run the `--write` merge without writing, print what it would change, and exit `1` if anything would change. Read-only. |
|
|
78
|
+
|
|
79
|
+
The four flags are mutually exclusive; passing two different
|
|
80
|
+
ones is a usage error.
|
|
77
81
|
|
|
78
82
|
`--write` is the only mode that touches the filesystem. It
|
|
79
83
|
operates **only** inside `configuration.signature_paths`
|
|
80
84
|
(default `sig/`); anything outside that tree is reported as
|
|
81
85
|
`skipped_outside_sig_root` without being written to.
|
|
82
86
|
|
|
87
|
+
### Keeping `sig/` current in CI
|
|
88
|
+
|
|
89
|
+
`--check` is the freshness gate. It takes the same options as
|
|
90
|
+
`--write` and fails exactly when that `--write` would create or
|
|
91
|
+
change a file, or would refuse one:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
rigor sig-gen --check lib
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
would create sig/greeter.rbs (1 method(s))
|
|
99
|
+
+ def greet: (String name) -> String
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
It also fails while a method stands refused
|
|
103
|
+
(`sig.skipped.inline-differs`), since `--write` refuses it
|
|
104
|
+
too. It exits `0` and prints `sig/ is up to date`
|
|
105
|
+
otherwise. Under `--format=json` the payload is
|
|
106
|
+
`{"up_to_date": …, "results": […], "refused": […]}`: one
|
|
107
|
+
entry per target in the `--write` JSON shape, with `action`
|
|
108
|
+
reading `would_create` / `would_update`, and one per refused
|
|
109
|
+
method.
|
|
110
|
+
|
|
111
|
+
The gate follows `--write`, not `--diff`
|
|
112
|
+
([ADR-112](../adr/112-extrbs-comment-channel.md) WD4 records
|
|
113
|
+
the departure). A `tighter-return` against a declaration that
|
|
114
|
+
already exists is a proposal `--write` declines without
|
|
115
|
+
`--overwrite`, so it does not fail `--check` either: a
|
|
116
|
+
reviewed, deliberately wider type must not fail the gate
|
|
117
|
+
forever. `--check --overwrite`
|
|
118
|
+
counts it, because `--write --overwrite` would apply it. Pass
|
|
119
|
+
`--check` the `--params` and `--effect-envelopes` flags your
|
|
120
|
+
`--write` uses, or it checks a different output.
|
|
121
|
+
|
|
83
122
|
## The classification model
|
|
84
123
|
|
|
85
|
-
Every method `rigor sig-gen` considers lands in one of
|
|
124
|
+
Every method `rigor sig-gen` considers lands in one of six
|
|
86
125
|
states:
|
|
87
126
|
|
|
88
127
|
| Classification | Meaning |
|
|
89
128
|
| --- | --- |
|
|
90
129
|
| `new-file` | No RBS file declares the receiver class at all. |
|
|
91
130
|
| `new-method` | RBS file declares the class but not this method. |
|
|
92
|
-
| `tighter-return` | RBS file declares the method, but the inferred return is a strict subtype of the declared return. |
|
|
93
|
-
| `
|
|
131
|
+
| `tighter-return` | RBS file declares the method, but the inferred return is a strict subtype of the declared return. The proposal is the declared line with only its return replaced; see [Tighter returns keep the declared parameters](#tighter-returns-keep-the-declared-parameters). |
|
|
132
|
+
| `inline-overwrite` | Only under `--overwrite`: a method declared inline with `# @rbs` / `#:` whose `sig/` declaration disagrees with it; the whole `sig/` member is replaced by the inline one. See [Methods declared inline](#methods-declared-inline). |
|
|
133
|
+
| `equivalent` | Nothing for `sig-gen` to propose: the inferred return is identical, wider or unrelated, or it is a narrowing the generator declines (a literal under a wider declaration, anything under a declared `void`, an overloaded declaration or an `alias`), or the method is an `initialize` the class already declares. Silently skipped. |
|
|
94
134
|
| `skipped` | Disqualified for one of the reasons below. |
|
|
95
135
|
|
|
96
136
|
The `sig.skipped.*` reasons are:
|
|
@@ -108,6 +148,17 @@ The `sig.skipped.*` reasons are:
|
|
|
108
148
|
- `sig.skipped.user-authored` — `--overwrite` was not set
|
|
109
149
|
and the method's existing RBS declaration would have to
|
|
110
150
|
be replaced.
|
|
151
|
+
- `sig.skipped.inline-declared` — `.rigor.yml` sets
|
|
152
|
+
`sig_gen.inline_declared: skip` and the method is declared
|
|
153
|
+
inline. See [Methods declared inline](#methods-declared-inline).
|
|
154
|
+
- `sig.skipped.inline-generic-class` — the method's class is
|
|
155
|
+
generic by an inline declaration and `sig/` does not declare
|
|
156
|
+
it yet, or names its type parameters otherwise. See
|
|
157
|
+
[Classes made generic inline](#classes-made-generic-inline).
|
|
158
|
+
- `sig.skipped.inline-differs` — the method is declared
|
|
159
|
+
inline and in `sig/`, and the two disagree. A refusal:
|
|
160
|
+
`--write` and `--check` exit `1`. See
|
|
161
|
+
[Methods declared inline](#methods-declared-inline).
|
|
111
162
|
- `sig.skipped.unrenderable-rbs` — the signature Rigor
|
|
112
163
|
rendered for this method does not parse as RBS. This one
|
|
113
164
|
is a **bug in Rigor**, not a property of your code: every
|
|
@@ -119,6 +170,308 @@ The `sig.skipped.*` reasons are:
|
|
|
119
170
|
the skipped method is reported on stderr, and it is worth
|
|
120
171
|
reporting to us.
|
|
121
172
|
|
|
173
|
+
### Tighter returns keep the declared parameters
|
|
174
|
+
|
|
175
|
+
A `tighter-return` changes the return and nothing else. The
|
|
176
|
+
visibility, the overload's annotations, the method type
|
|
177
|
+
parameters, the parameter list and the block are copied from
|
|
178
|
+
the declaration as written, so applying the proposal never
|
|
179
|
+
changes which calls the signature accepts:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
$ rigor sig-gen --diff lib/c.rb
|
|
183
|
+
--- lib/c.rb: C#m
|
|
184
|
+
- def m: (Integer x) -> untyped
|
|
185
|
+
+ def m: (Integer x) -> nil
|
|
186
|
+
|
|
187
|
+
--- lib/c.rb: C#n
|
|
188
|
+
- def n: (?) -> untyped
|
|
189
|
+
+ def n: (?) -> nil
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The `-` line is the declaration the proposal replaces. A method
|
|
193
|
+
declared only on an ancestor is different: the proposal adds an
|
|
194
|
+
override to the method's own class, and it is written from that
|
|
195
|
+
class's `def` like a new method, with `untyped` parameters. The
|
|
196
|
+
ancestor's parameters describe the ancestor's `def`, and the
|
|
197
|
+
override may take other arguments.
|
|
198
|
+
|
|
199
|
+
Two declarations get no proposal:
|
|
200
|
+
|
|
201
|
+
- One with more than one overload
|
|
202
|
+
(`def m: (Integer) -> untyped | (String) -> untyped`). The
|
|
203
|
+
body is typed once for all of its overloads, so the inferred
|
|
204
|
+
return does not say which overload returns what. Merging the
|
|
205
|
+
overloads into one line would drop them, and giving every
|
|
206
|
+
overload the same return would widen the ones you wrote
|
|
207
|
+
narrower.
|
|
208
|
+
- A name the class itself declares through `alias al m`. Its
|
|
209
|
+
parameters are `m`'s, and the proposal would read as a
|
|
210
|
+
rewrite of `m`'s line. An alias only an ancestor declares is
|
|
211
|
+
overridden like any other ancestor declaration.
|
|
212
|
+
|
|
213
|
+
An `initialize` your `sig/` already declares is `equivalent`
|
|
214
|
+
too. sig-gen writes a constructor stub only for a class that
|
|
215
|
+
does not declare one. The exception is `--params=observed`:
|
|
216
|
+
when the observed argument types would replace an `untyped` the
|
|
217
|
+
declaration still has, the stub is proposed so that
|
|
218
|
+
`--overwrite` can apply it.
|
|
219
|
+
|
|
220
|
+
## Methods declared inline
|
|
221
|
+
|
|
222
|
+
A method you annotated with `# @rbs` or `#:` already has a
|
|
223
|
+
contract, written next to the code. `sig-gen` does not infer
|
|
224
|
+
one for it; it copies yours into `sig/`, so the generated
|
|
225
|
+
signature is the whole contract your gem ships:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
class Greeter
|
|
229
|
+
# @rbs name: String
|
|
230
|
+
# @rbs return: String
|
|
231
|
+
def greet(name) = "Hello, #{name}"
|
|
232
|
+
|
|
233
|
+
#: () -> Integer
|
|
234
|
+
def count = 1
|
|
235
|
+
|
|
236
|
+
# @rbs num: Float
|
|
237
|
+
def pair(num) = [num, num.to_s]
|
|
238
|
+
end
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
$ rigor sig-gen
|
|
243
|
+
# lib/greeter.rb
|
|
244
|
+
class Greeter
|
|
245
|
+
# [new]
|
|
246
|
+
def greet: (String name) -> String
|
|
247
|
+
# [new]
|
|
248
|
+
def count: () -> Integer
|
|
249
|
+
# [new]
|
|
250
|
+
def pair: (Float num) -> [Float, String]
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`count` is written as `Integer`, not the `1` its body proves:
|
|
255
|
+
the declaration is what you meant, and inference does not
|
|
256
|
+
override it. `pair` declares its parameter and not its return,
|
|
257
|
+
so the parameter is copied and the return comes from the body,
|
|
258
|
+
the same split [ADR-107](../adr/107-checked-types-and-typeless-comments.md)
|
|
259
|
+
draws between authored parameters and generated returns. An
|
|
260
|
+
`initialize` is always written `-> void`, whatever its body
|
|
261
|
+
ends with. A member annotation you wrote inline, such as
|
|
262
|
+
`# @rbs %a{deprecated}`, is copied with it. A method with no
|
|
263
|
+
annotation of its own is proposed exactly as in any other
|
|
264
|
+
file.
|
|
265
|
+
|
|
266
|
+
Once `sig/` holds the copy, `rigor check` reads it beside the
|
|
267
|
+
inline declaration, and an identical pair is quiet
|
|
268
|
+
([ADR-112](../adr/112-extrbs-comment-channel.md) WD5).
|
|
269
|
+
sig-gen compares the two on every run, `attr_*` declarations
|
|
270
|
+
included. They are compared as types, not text:
|
|
271
|
+
parameter names, spacing, how a union is spelled (`String?`
|
|
272
|
+
and `String | nil`, `Integer | String` and `String |
|
|
273
|
+
Integer`, `bool` and `true | false`) and a leading `::` do
|
|
274
|
+
not count. The `::` only counts when it changes what the name
|
|
275
|
+
means: inside `module NS`, `Foo` is `NS::Foo` if `NS`
|
|
276
|
+
declares one, and then it is not `::Foo`. Overload order does
|
|
277
|
+
count, because RBS answers a call with the first overload that
|
|
278
|
+
matches. When the two agree, and `sig/` carries every
|
|
279
|
+
annotation you wrote inline, there is nothing to do.
|
|
280
|
+
|
|
281
|
+
When they differ, sig-gen does not pick a side. The inline
|
|
282
|
+
annotation may be your newer edit; the `sig/` member may be a
|
|
283
|
+
reviewed contract someone changed on purpose. The method is
|
|
284
|
+
refused as `sig.skipped.inline-differs`: nothing is written,
|
|
285
|
+
`--write` prints a `REFUSED` line and exits `1` (under
|
|
286
|
+
`--format=json` the refused methods are listed under
|
|
287
|
+
`refused`), and `--check` fails, so the contradiction cannot
|
|
288
|
+
pass CI unnoticed. Resolve it one of two ways:
|
|
289
|
+
|
|
290
|
+
- Keep the inline declaration: re-run with `--overwrite`. The
|
|
291
|
+
whole `sig/` member is replaced by the inline line, as
|
|
292
|
+
rbs-inline reads it — a parameter you left unannotated is
|
|
293
|
+
`untyped`, an unannotated `&block` is `?{ (?) -> untyped }`
|
|
294
|
+
— and an `attr_*` declaration is replaced by the inline
|
|
295
|
+
attribute in the same spelling. The two sides are never
|
|
296
|
+
mixed slot by slot: their overloads and type variables need
|
|
297
|
+
not correspond. That also means an overload written only in
|
|
298
|
+
`sig/` is dropped, and a caller that relied on it can stop
|
|
299
|
+
type-checking; read the `--diff` first. Comments inside the
|
|
300
|
+
replaced member, and annotations already on it, are kept.
|
|
301
|
+
- Keep the `sig/` member: edit or delete the inline annotation
|
|
302
|
+
to match.
|
|
303
|
+
|
|
304
|
+
A `sig/` member written `def m: ... | ...` is not a copy: it
|
|
305
|
+
adds overloads to the inline declaration, and sig-gen leaves it
|
|
306
|
+
alone.
|
|
307
|
+
|
|
308
|
+
A parameter-only annotation (`# @rbs name: String`, no
|
|
309
|
+
`return:`) states the parameters and nothing else, so only the
|
|
310
|
+
parameters are compared. When they match, the return in `sig/`
|
|
311
|
+
is weighed like any declared return: a `-> void`, a type wider
|
|
312
|
+
than what the body proves, or a literal the body happens to
|
|
313
|
+
return (`-> String` over `"x"`) is left as it is, and a return
|
|
314
|
+
the body proves strictly narrower is a `tighter-return`
|
|
315
|
+
proposal, applied only with `--overwrite`. When the parameters
|
|
316
|
+
differ, the method is refused even under `--overwrite`: the
|
|
317
|
+
body is typed under the parameters the `sig/` member declares,
|
|
318
|
+
so a return inferred for the new line would describe
|
|
319
|
+
parameters that are about to change. Delete the `sig/` member
|
|
320
|
+
and re-run; sig-gen then writes the method afresh.
|
|
321
|
+
|
|
322
|
+
### Classes made generic inline
|
|
323
|
+
|
|
324
|
+
sig-gen does not write a class's type parameters yet. A class
|
|
325
|
+
declared generic inline (`# @rbs generic T`) is therefore not
|
|
326
|
+
opened in `sig/`: a header without its parameters would make
|
|
327
|
+
rbs reject the class, and every class whose signature mentions
|
|
328
|
+
it, with `GenericParameterMismatchError`. Its methods, and
|
|
329
|
+
those of classes nested in it, are skipped as
|
|
330
|
+
`sig.skipped.inline-generic-class`. Declare the class in
|
|
331
|
+
`sig/` with the same parameters (`class Box[T]` ... `end`) and
|
|
332
|
+
sig-gen writes the members into that declaration. A `sig/`
|
|
333
|
+
declaration that names them otherwise (`class Box[U]`) is
|
|
334
|
+
skipped the same way: a copied `-> T` would name a parameter
|
|
335
|
+
nothing binds.
|
|
336
|
+
|
|
337
|
+
### Projects that run Steep on the same annotations
|
|
338
|
+
|
|
339
|
+
If Steep reads your inline annotations (`check "lib", inline:
|
|
340
|
+
true` beside `signature "sig"`), a copy in `sig/` is a second
|
|
341
|
+
declaration of each method, and Steep rejects the class with
|
|
342
|
+
`DuplicatedMethodDefinition`. Tell `sig-gen` to leave those
|
|
343
|
+
methods out:
|
|
344
|
+
|
|
345
|
+
```yaml
|
|
346
|
+
sig_gen:
|
|
347
|
+
inline_declared: skip
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Every method the inline reader declares is then skipped as
|
|
351
|
+
`sig.skipped.inline-declared`. That includes the un-annotated
|
|
352
|
+
`def`s of a file that carries any annotation, because the
|
|
353
|
+
reader declares those too (as `untyped`), and so does rbs's
|
|
354
|
+
own inline parser, which Steep's `inline: true` uses. A file
|
|
355
|
+
with no annotation at all is not read inline by Rigor, so its
|
|
356
|
+
methods are still written; if Steep reads that file inline,
|
|
357
|
+
keep it out of the paths you give `sig-gen`.
|
|
358
|
+
|
|
359
|
+
What the setting costs: `sig/` is no longer the whole
|
|
360
|
+
contract. A consumer that reads only your shipped `sig/` —
|
|
361
|
+
Rigor or Steep in a project that depends on your gem — never
|
|
362
|
+
sees the skipped methods. Their inline annotations, including
|
|
363
|
+
a Rigor refinement written there, take effect only where the
|
|
364
|
+
source itself is analysed, which is your own project.
|
|
365
|
+
|
|
366
|
+
## Emitting effect annotations
|
|
367
|
+
|
|
368
|
+
If your `.rigor.yml` carries an `effects:` block, `sig-gen`
|
|
369
|
+
writes one more thing: `%a{pure}`, the purity annotation rbs
|
|
370
|
+
and Steep already understand, above the methods whose whole
|
|
371
|
+
footprint Rigor read and found to be nothing.
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
class Label
|
|
375
|
+
def render
|
|
376
|
+
parts = []
|
|
377
|
+
parts << "a"
|
|
378
|
+
parts.join
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
$ rigor sig-gen
|
|
385
|
+
# lib/label.rb
|
|
386
|
+
class Label
|
|
387
|
+
# [new]
|
|
388
|
+
%a{pure}
|
|
389
|
+
def render: () -> String
|
|
390
|
+
end
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Five conditions have to hold before an annotation is written,
|
|
394
|
+
and every one of them exists to keep a wrong one off the
|
|
395
|
+
page. An emitted annotation is not a hint — the effects opt-in reads
|
|
396
|
+
it back as an **envelope** and enforces it on the method and
|
|
397
|
+
on everything the method reaches, so a `%a{pure}` `sig-gen`
|
|
398
|
+
invented would put `effect.envelope-exceeded` on code that
|
|
399
|
+
is correct.
|
|
400
|
+
|
|
401
|
+
- The summary must be **exhaustive**: every call the method
|
|
402
|
+
reaches was resolved. A summary that is not reads "these
|
|
403
|
+
effects, and possibly more", which is exactly the claim an
|
|
404
|
+
envelope must not make.
|
|
405
|
+
- The summary must be **undischarged**: nothing in the
|
|
406
|
+
method's footprint may be invisible only because
|
|
407
|
+
`effects.tolerated:` says to ignore it. A method whose
|
|
408
|
+
whole footprint is a tolerated `telemetry` call looks
|
|
409
|
+
clean to *your* project and to nobody else — not to a
|
|
410
|
+
consumer reading your shipped `sig/`, and not to your own
|
|
411
|
+
`--no-tolerated-effects` audit.
|
|
412
|
+
- Every callee must be **described by something**: a
|
|
413
|
+
catalogue row, a plugin, an envelope, or a definition in
|
|
414
|
+
your own project. "Every call resolved" and "every callee's
|
|
415
|
+
footprint is known" are different questions. A method whose
|
|
416
|
+
body is one call into a gem nobody has written a row or an
|
|
417
|
+
envelope for is exhaustive and tells you nothing, so it is
|
|
418
|
+
left bare rather than called pure.
|
|
419
|
+
- The method must not **already carry a bound of its own**.
|
|
420
|
+
An annotation on the method or on its class — in `sig/` or
|
|
421
|
+
as an rbs-inline `# @rbs %a{…}` — or an `effects.envelopes:`
|
|
422
|
+
stanza selecting it by `namespace:` or by `match:`, is a
|
|
423
|
+
contract you wrote about this body; sig-gen will not replace
|
|
424
|
+
it with an inference about the same body. A bound whose
|
|
425
|
+
label is misspelled counts too: it bounds nothing, but
|
|
426
|
+
overwriting it would delete the annotation the
|
|
427
|
+
`effect.unknown-label` report points at.
|
|
428
|
+
- The `≤` lane must be **empty**. A callee that states its
|
|
429
|
+
own bound puts that claim in your method's declared lane
|
|
430
|
+
without proving anything, so `rigor effects` shows
|
|
431
|
+
`[] ≤ [io.net.http]` where the proven lane is empty. Rigor
|
|
432
|
+
will not write `%a{pure}` over a claim it never proved
|
|
433
|
+
away.
|
|
434
|
+
|
|
435
|
+
`--effect-envelopes` adds the labelled spelling,
|
|
436
|
+
`%a{rigor:v1:effect io.db, nondet.time}`, for methods that
|
|
437
|
+
do have a footprint. It is a separate flag because `%a{pure}`
|
|
438
|
+
is the ecosystem's annotation and this one is Rigor's: a
|
|
439
|
+
labelled envelope in your `sig/` is a Rigor-specific contract,
|
|
440
|
+
and you should ask for it by name.
|
|
441
|
+
|
|
442
|
+
Under `--write`, an annotation goes on the line above the
|
|
443
|
+
declaration it binds. A declaration that **already** carries
|
|
444
|
+
annotations is left byte-untouched and reported as
|
|
445
|
+
`sig.effect.left-unreadable`: the writer cannot tell an
|
|
446
|
+
annotation it wrote from one you wrote, and it has no grammar
|
|
447
|
+
for merging two, so it will not rewrite that region. Decide
|
|
448
|
+
what it should say and write it yourself.
|
|
449
|
+
|
|
450
|
+
The `sig.effect.*` reasons are:
|
|
451
|
+
|
|
452
|
+
- `sig.effect.emitted` — an annotation was rendered.
|
|
453
|
+
- `sig.effect.withheld-tolerated` — the footprint is only
|
|
454
|
+
clean under `effects.tolerated:`.
|
|
455
|
+
- `sig.effect.withheld-non-exhaustive` — some call the
|
|
456
|
+
method reaches could not be resolved.
|
|
457
|
+
- `sig.effect.withheld-unclaimed-callee` — some call it
|
|
458
|
+
reaches resolved, and nothing anywhere says what that
|
|
459
|
+
callee does.
|
|
460
|
+
- `sig.effect.withheld-declared` — the method already carries
|
|
461
|
+
an authored bound, or a label survives in the `≤` lane.
|
|
462
|
+
- `sig.effect.left-unreadable` — the target declaration
|
|
463
|
+
already carries annotations, so nothing was written there.
|
|
464
|
+
|
|
465
|
+
One limit worth knowing: annotations ride on the signature
|
|
466
|
+
lines `sig-gen` proposes, so a method whose declaration is
|
|
467
|
+
already exactly right gets none. An up-to-date `sig/` is
|
|
468
|
+
therefore not annotated in place; the reader for those is
|
|
469
|
+
`rigor effects --pure`, and writing them is still a hand
|
|
470
|
+
edit.
|
|
471
|
+
|
|
472
|
+
With no `effects:` block in `.rigor.yml`, none of this runs
|
|
473
|
+
and the output is byte-for-byte what it was before.
|
|
474
|
+
|
|
122
475
|
The three `sig.generated.*` identifiers
|
|
123
476
|
(`sig.generated.new-file` / `new-method` / `tighter-return`)
|
|
124
477
|
are emitted as JSON fields under `--format=json` so CI
|
|
@@ -219,7 +572,14 @@ list is the current state):
|
|
|
219
572
|
RBS.
|
|
220
573
|
- **`attr_reader` / `attr_writer` / `attr_accessor`** with
|
|
221
574
|
literal Symbol arguments. The return type is the
|
|
222
|
-
accumulated ivar type from `Scope#class_ivars_for`.
|
|
575
|
+
accumulated ivar type from `Scope#class_ivars_for`. A
|
|
576
|
+
writer stores whatever its caller passes, so an
|
|
577
|
+
`attr_writer` / `attr_accessor` ivar reads untyped.
|
|
578
|
+
The accessor, and any other method returning that
|
|
579
|
+
ivar, is skipped even beside a concrete write such as
|
|
580
|
+
`@logger = Logger.new`, unless `--params=observed`
|
|
581
|
+
types the constructor parameter the ivar is assigned
|
|
582
|
+
from. The
|
|
223
583
|
generator emits the long-form `def name: () -> T`
|
|
224
584
|
spelling so the writer's merge path applies unchanged;
|
|
225
585
|
existing short-form `attr_reader name: T` declarations
|
|
@@ -243,7 +603,7 @@ two are wired today, one is reserved.
|
|
|
243
603
|
| Policy | Behaviour |
|
|
244
604
|
| --- | --- |
|
|
245
605
|
| `untyped` (default) | Every parameter is spelled `untyped`. No inference-derived parameter contract is imposed on future callers. The user retains complete authorship over parameter typing. |
|
|
246
|
-
| `observed` | Collect argument types from every call site under `--observe=PATH...` (defaults to `spec/`
|
|
606
|
+
| `observed` | Collect argument types from every call site under `--observe=PATH...` (defaults to the configured `test_paths:`, or whichever of `spec/` and `test/` exist), union per parameter position, erase to RBS, emit the union. With no test root to observe, or a declared root that does not exist, sig-gen says so on stderr. |
|
|
247
607
|
| `observed-strict` | Reserved. Will additionally widen to capability roles (`_ToStr`, `_ToS`, …) once the role catalog ships. Currently rejected with a usage error. |
|
|
248
608
|
|
|
249
609
|
The default deliberately favours `untyped` because of
|
|
@@ -261,9 +621,76 @@ parameter contract I want."* That is a correctness-
|
|
|
261
621
|
preserving widening — every existing caller still passes —
|
|
262
622
|
but it does narrow the contract relative to `untyped`.
|
|
263
623
|
|
|
624
|
+
## Type aliases your project declares
|
|
625
|
+
|
|
626
|
+
When a proposal's type is a union whose members are exactly
|
|
627
|
+
the expansion of a `type` alias declared in your own
|
|
628
|
+
`sig/`, the alias name is what gets emitted. For a method
|
|
629
|
+
on `Rigor::Type::Combinator`, sig-gen emits
|
|
630
|
+
|
|
631
|
+
```
|
|
632
|
+
def hash_shape_keys: (untyped) -> ::Rigor::Type::t
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
rather than the twenty-two members `Rigor::Type::t`
|
|
636
|
+
expands to. The name is written in its absolute form so it
|
|
637
|
+
cannot rebind to a nearer constant of the same spelling.
|
|
638
|
+
|
|
639
|
+
This applies wherever sig-gen renders a type, not only to
|
|
640
|
+
returns: `--params=observed` parameter positions, `attr_*`
|
|
641
|
+
accessors, and `Data` / `Struct` members all fold the same
|
|
642
|
+
way. The `--format=json` payload carries the folded
|
|
643
|
+
spelling in its `rbs` field; `inferred_return` is the
|
|
644
|
+
carrier the engine computed and stays expanded.
|
|
645
|
+
|
|
646
|
+
The rule is member-set equality, so a proposal missing one
|
|
647
|
+
arm of the alias still prints in full — the alias names a
|
|
648
|
+
type the method cannot return, and claiming it would be
|
|
649
|
+
wrong rather than merely verbose.
|
|
650
|
+
|
|
651
|
+
A wrong alias name is worse than a long union: it is an RBS
|
|
652
|
+
claim you did not make. Four rules keep the fold from
|
|
653
|
+
guessing.
|
|
654
|
+
|
|
655
|
+
**Your aliases only.** The candidates come from your
|
|
656
|
+
configured `signature_paths:` (or `sig/` when you
|
|
657
|
+
configured none) — not from gems in your bundle, not from
|
|
658
|
+
an `rbs collection` tree, and not from core or stdlib RBS.
|
|
659
|
+
Core declares `Warning::category` as `:deprecated |
|
|
660
|
+
:experimental | :performance`; a project that never wrote
|
|
661
|
+
that alias does not get it in its proposals. A loaded
|
|
662
|
+
plugin's own signature directory is subtracted even when
|
|
663
|
+
you listed it in `signature_paths:` yourself — those
|
|
664
|
+
aliases are the plugin's vocabulary.
|
|
665
|
+
|
|
666
|
+
**Namespace proximity.** An alias is only offered to a
|
|
667
|
+
method whose owner is inside the alias's own namespace, and
|
|
668
|
+
the nearest such alias wins — most specific first, then
|
|
669
|
+
declaration order by (file, line, name). Without this,
|
|
670
|
+
`:positive | :negative` anywhere in a codebase would pick
|
|
671
|
+
up any alias that happens to name that pair.
|
|
672
|
+
|
|
673
|
+
**Unambiguous alias bodies only.** Some RBS forms reach the
|
|
674
|
+
renderer looking like something else: an intersection can
|
|
675
|
+
read as one of its members, a proc type as a bare `Proc`, a
|
|
676
|
+
nested alias as whatever it expanded to. An alias whose
|
|
677
|
+
body contains one of those is skipped, because a fold into
|
|
678
|
+
it would put a type in your signature that the method does
|
|
679
|
+
not return. Only class instances, singletons, literals,
|
|
680
|
+
unions, optionals, tuples and the `nil` / `bool` / `bot`
|
|
681
|
+
bases qualify.
|
|
682
|
+
|
|
683
|
+
**Unions only, non-generic only.** A `type name = String`
|
|
684
|
+
alias never rewrites an ordinary `String` return, and a
|
|
685
|
+
generic alias (`type boxed[T] = ...`) has no fixed member
|
|
686
|
+
set to match.
|
|
687
|
+
|
|
688
|
+
A project that declares no aliases gets byte-for-byte the
|
|
689
|
+
output it got before.
|
|
690
|
+
|
|
264
691
|
## RSpec-aware observations
|
|
265
692
|
|
|
266
|
-
When
|
|
693
|
+
When the observed test roots hold an RSpec suite, the
|
|
267
694
|
generator recognises three RSpec-shaped binding patterns
|
|
268
695
|
and uses them to type receivers that would otherwise
|
|
269
696
|
degrade to `Dynamic[top]`:
|
|
@@ -289,6 +716,13 @@ across nested scopes are last-wins; the recogniser does not
|
|
|
289
716
|
re-implement RSpec's full scope rules — the typical
|
|
290
717
|
one-spec-file shape is the target.
|
|
291
718
|
|
|
719
|
+
A Minitest suite under `test/` is observed too, with no
|
|
720
|
+
recogniser of its own: a call whose receiver types to one
|
|
721
|
+
class counts. A receiver assigned in `setup` is the gap —
|
|
722
|
+
inside a `test_*` method it reads as `Foo | nil`, and such
|
|
723
|
+
a call is not observed yet
|
|
724
|
+
([#1389](https://github.com/rigortype/rigor/issues/1389)).
|
|
725
|
+
|
|
292
726
|
The recogniser is part of the generator itself; you do not
|
|
293
727
|
need to install `rigor-rspec` to benefit from it. If you
|
|
294
728
|
already use `rigor-rspec` for diagnostics, the two run side
|
|
@@ -308,9 +742,16 @@ by side without coordination.
|
|
|
308
742
|
tree.
|
|
309
743
|
- **Will not** replace an existing method declaration
|
|
310
744
|
unless `--overwrite` is set AND the candidate is a
|
|
311
|
-
`tighter-return
|
|
312
|
-
|
|
313
|
-
|
|
745
|
+
`tighter-return`, and then only its return changes: the
|
|
746
|
+
rest of the declaration is kept. Without `--overwrite`,
|
|
747
|
+
existing declarations are user-authored and the new method
|
|
748
|
+
is silently skipped.
|
|
749
|
+
- **Will not** change an existing method declaration that
|
|
750
|
+
disagrees with the method's inline declaration unless
|
|
751
|
+
`--overwrite` is set; it reports the method as `REFUSED`
|
|
752
|
+
and exits `1`. With `--overwrite` the whole member is
|
|
753
|
+
replaced by the inline declaration (`inline-overwrite`),
|
|
754
|
+
keeping the comments and annotations already on it.
|
|
314
755
|
- **Will not** touch `attr_reader` / `attr_writer` /
|
|
315
756
|
`attr_accessor` declarations in existing RBS — those are
|
|
316
757
|
always treated as user-authored.
|
|
@@ -362,8 +803,8 @@ A typical iteration on a new file:
|
|
|
362
803
|
# 1. See what Rigor would propose.
|
|
363
804
|
rigor sig-gen lib/calc.rb
|
|
364
805
|
|
|
365
|
-
# 2. Run with the observed-params policy to use
|
|
366
|
-
# a parameter-type signal.
|
|
806
|
+
# 2. Run with the observed-params policy to use the test
|
|
807
|
+
# roots (`test_paths:`) as a parameter-type signal.
|
|
367
808
|
rigor sig-gen --params=observed lib/calc.rb
|
|
368
809
|
|
|
369
810
|
# 3. Compare against the current sig/ tree.
|