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
|
@@ -45,40 +45,97 @@ plugin id/version + config), so an unchanged second run skips the parse.
|
|
|
45
45
|
| Rule | Severity | Fires when |
|
|
46
46
|
| --- | --- | --- |
|
|
47
47
|
| `plugin.rbs-inline.source-rbs-synthesis-failed` | info | rbs-inline could not parse a file; analysis falls back to no inline-RBS contribution and the diagnostic carries the upstream error |
|
|
48
|
-
| `plugin.rbs-inline.source-rbs-annotation-not-honoured` | info | an annotation parsed successfully but contributed nothing — the file's other annotations still apply.
|
|
48
|
+
| `plugin.rbs-inline.source-rbs-annotation-not-honoured` | info | an annotation parsed successfully but contributed nothing — the file's other annotations still apply. Six causes: a member your `sig/` also declares where Rigor cannot tell which of the two is more precise (see [Precedence](#precedence)), the `# @rbs module-self: Foo` spelling (see below), a `#:` line whose type does not parse (see [Unparseable `#:` types](#unparseable--types)), a same-line `# @rbs %a{…}` whose method type does not parse (see [Same-line annotations](#same-line-annotations)), a `# @rbs name: T` parameter type that does not parse (see [Unparseable `# @rbs name:` types](#unparseable--rbs-name-types)), and an `@rbs`-prefixed tag the gem does not recognise (see [An unrecognised tag after `@rbs`](#an-unrecognised-tag-after-rbs)) |
|
|
49
|
+
| `rbs.contradicting-signature` | error | an inline declaration contradicts the `sig/` declaration of the same method, or an inline `%a{rigor:v1:…}` refinement is outside its own declared type (see [Precedence](#precedence)). A core rule rather than the plugin's own, so it carries no `plugin.rbs-inline.` prefix |
|
|
49
50
|
|
|
50
51
|
## Precedence
|
|
51
52
|
|
|
52
53
|
When a method is declared **both** in `sig/` and by an inline
|
|
53
|
-
annotation, **the
|
|
54
|
-
|
|
55
|
-
binds
|
|
54
|
+
annotation, Rigor **compares the two**, position by position — each
|
|
55
|
+
parameter, the return type, a block's parameters and return — and
|
|
56
|
+
exactly one of them binds. Every other annotation in the file still
|
|
57
|
+
binds either way, and the class keeps its method surface.
|
|
56
58
|
|
|
57
59
|
```ruby
|
|
58
|
-
# lib/demo.rb
|
|
59
|
-
class Demo
|
|
60
|
-
# @rbs
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
# @rbs (Integer) ->
|
|
64
|
-
def
|
|
60
|
+
# lib/demo.rb # sig/demo.rbs
|
|
61
|
+
class Demo # class Demo
|
|
62
|
+
# @rbs dir: :asc | :desc # def order: (Symbol dir) -> void
|
|
63
|
+
def order(dir) = nil # def shared: (String) -> Integer
|
|
64
|
+
# end
|
|
65
|
+
# @rbs (Integer) -> String
|
|
66
|
+
def shared(v) = v.to_s
|
|
65
67
|
end
|
|
66
68
|
```
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
- **One refines the other: the more precise one binds, silently.**
|
|
71
|
+
`:asc | :desc` is a subtype of `Symbol`, so `Demo#order` takes the
|
|
72
|
+
inline contract and `order(:up)` is an argument-type error, where
|
|
73
|
+
the `.rbs` alone let it pass. The merge takes the narrower type in
|
|
74
|
+
every position, parameters included, on purpose: both declarations
|
|
75
|
+
are yours, and the narrower one is what you stated. `String` in
|
|
76
|
+
`sig/` beside `non-empty-string` inline reads as `non-empty-string`.
|
|
77
|
+
`untyped`, `void` and `top` are consistent with anything and say the
|
|
78
|
+
least, so `sig/`'s `-> untyped` beside an inline `-> void` is quiet.
|
|
79
|
+
Identical declarations — what `rigor sig-gen` writes for an annotated
|
|
80
|
+
method — are quiet too. The inline side binds only if the `.rbs`
|
|
81
|
+
member loses nothing by it: the same visibility, and every
|
|
82
|
+
annotation it carries (a predicate, an assertion, an effect
|
|
83
|
+
envelope) also on the inline one.
|
|
84
|
+
- **They contradict: an error, and the `.rbs` binds.** If `Demo#shared`
|
|
85
|
+
took a `::String` in `sig/` and an `::Integer` inline, no value would
|
|
86
|
+
be both, so the run would report
|
|
87
|
+
[`rbs.contradicting-signature`](../04-diagnostics.md#rule-rbs-contradicting-signature)
|
|
88
|
+
at the `sig/` line, naming the annotated file. Spelled `String` and
|
|
89
|
+
`Integer`, as in the example, the pair is undecided instead (below). Positional counts that
|
|
90
|
+
cannot meet, or a keyword one side requires and the other cannot take
|
|
91
|
+
in any form, contradict the same way. The error needs a proof, and
|
|
92
|
+
only Ruby core or stdlib classes written absolutely (`::String`,
|
|
93
|
+
`::Integer`) give one, read from their RBS hierarchy. A module such
|
|
94
|
+
as `Comparable` never counts, since any class may include it; nor
|
|
95
|
+
does a class of your own (your `sig/` may omit the superclass Ruby
|
|
96
|
+
gives it), a gem's class, a relative name such as plain `String`
|
|
97
|
+
(it may be your own `App::String`), an optional or rest parameter
|
|
98
|
+
(a call may leave it out), or a block's parameters (the body may
|
|
99
|
+
never yield). A stale generated signature is the usual cause:
|
|
100
|
+
regenerate it, or fix the annotation.
|
|
101
|
+
- **Rigor cannot tell: the `.rbs` binds, with an `:info`.** When a
|
|
102
|
+
position names a type alias, an interface, `self`, a type variable,
|
|
103
|
+
or a relative class name your project also declares, when Rigor
|
|
104
|
+
cannot prove two types disjoint (a subclass relation such as
|
|
105
|
+
`Integer` against `Numeric` included), when the parameter lists are shaped
|
|
106
|
+
differently but overlap, when the overloads do not pair one to one
|
|
107
|
+
(they are paired by what they declare, not by their order), or when
|
|
108
|
+
each side is more precise somewhere, the inline signature is dropped
|
|
109
|
+
and reported as
|
|
110
|
+
`plugin.rbs-inline.source-rbs-annotation-not-honoured`, naming the
|
|
111
|
+
member and the `.rbs` that bound. Make one a refinement of the
|
|
112
|
+
other, or delete one, to settle it.
|
|
113
|
+
|
|
114
|
+
`rigor sig-gen --write` produces this overlap on purpose: by default it
|
|
115
|
+
copies each inline declaration into `sig/`, so the generated signature
|
|
116
|
+
is the complete contract a gem ships. An identical copy is quiet, as
|
|
117
|
+
above. When an inline annotation later disagrees with its copy,
|
|
118
|
+
`rigor sig-gen --write` and `--check` refuse the method and exit `1`
|
|
119
|
+
until you make the two agree or pass `--overwrite`, which replaces the
|
|
120
|
+
`sig/` member with the inline declaration. A project whose Steep reads the same
|
|
121
|
+
annotations sets `sig_gen.inline_declared: skip` instead
|
|
122
|
+
([handbook chapter 11](../../handbook/11-sig-gen.md#methods-declared-inline)).
|
|
123
|
+
|
|
124
|
+
A `%a{rigor:v1:return: …}` or `%a{rigor:v1:param: …}` refinement on an
|
|
125
|
+
inline annotation must also share values with its own declared type:
|
|
126
|
+
`# @rbs %a{rigor:v1:return: positive-int} () -> ::String` is reported as
|
|
127
|
+
`rbs.contradicting-signature` at the annotated file (the same proof rule
|
|
128
|
+
applies, so `-> String` without `::` is left alone).
|
|
129
|
+
|
|
130
|
+
There is no upstream rule to defer to: rbs merges an inline `.rb`
|
|
131
|
+
declaration and a `.rbs` one into a single class entry and ranks
|
|
132
|
+
neither, so Steep reports the same overlap as a signature error and
|
|
133
|
+
the class fails to build. Rigor keeps the reporting and drops the
|
|
134
|
+
degradation ([ADR-112](../../adr/112-extrbs-comment-channel.md) WD5,
|
|
135
|
+
which replaced [ADR-32](../../adr/32-rbs-inline-comment-ingestion.md)
|
|
136
|
+
WD13's "the `.rbs` always wins") — left to collide, one duplicated
|
|
137
|
+
method costs the class every other method, and each call on it, real
|
|
138
|
+
methods and typos alike, reads `Dynamic[top]`.
|
|
82
139
|
|
|
83
140
|
Two overlaps this does **not** cover: a `.rbs` that collides with
|
|
84
141
|
*bundled* RBS (Ruby core, stdlib, a gem's signatures) is quarantined
|
|
@@ -109,6 +166,136 @@ in silence. Constructs the gem supports and the built-in parser does not —
|
|
|
109
166
|
`@rbs generic T`, `@rbs!` embedded RBS blocks, `@rbs inherits`, method
|
|
110
167
|
visibility — all work here.
|
|
111
168
|
|
|
169
|
+
## Same-line annotations
|
|
170
|
+
|
|
171
|
+
An `%a{…}` annotation can sit on the same line as a method type:
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
class Reader
|
|
175
|
+
# @rbs %a{rigor:v1:return: non-empty-string} () -> String
|
|
176
|
+
def title = "x"
|
|
177
|
+
|
|
178
|
+
#: %a{pure} () -> String
|
|
179
|
+
def label = "x"
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
This is the spelling the built-in parser and Steep's inline mode accept. The
|
|
184
|
+
gem itself does not: it keeps the annotation and drops the method type in the
|
|
185
|
+
`@rbs` form, and drops the whole `#:` line. Rigor splits the line back into the
|
|
186
|
+
annotation and the method type before the gem's writer runs, so both apply, as
|
|
187
|
+
they do when the annotation has a line of its own
|
|
188
|
+
([ADR-32](../../adr/32-rbs-inline-comment-ingestion.md) WD11). The gem's own
|
|
189
|
+
`rbs-inline --output` is unchanged and still drops them.
|
|
190
|
+
|
|
191
|
+
When the method type after the annotations does not parse, nothing is split:
|
|
192
|
+
the method types as if no signature had been written, and Rigor reports it — a
|
|
193
|
+
`#:` line under [Unparseable `#:` types](#unparseable--types), and an `@rbs`
|
|
194
|
+
line as `plugin.rbs-inline.source-rbs-annotation-not-honoured` naming the line
|
|
195
|
+
and the text it could not read.
|
|
196
|
+
|
|
197
|
+
## Unparseable `#:` types
|
|
198
|
+
|
|
199
|
+
A `#:` line whose type does not parse as RBS is dropped — the signature never
|
|
200
|
+
applies, and the method types as if the line had never been written, not
|
|
201
|
+
merely as if its type were wrong:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
class BadRefProbe
|
|
205
|
+
#: (finite-float) -> String
|
|
206
|
+
def show(f)
|
|
207
|
+
f.to_s
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`finite-float` is a [Rigor refinement](../16-rbs-extended-annotations.md)
|
|
213
|
+
name, not an RBS type, and does not belong in an ordinary type position.
|
|
214
|
+
Rigor reports the drop as
|
|
215
|
+
`plugin.rbs-inline.source-rbs-annotation-not-honoured`, naming the line and
|
|
216
|
+
the text that failed to parse, rather than leaving `show` silently `untyped`
|
|
217
|
+
with no diagnostic anywhere. The `# @rbs name: TYPE` tag form of the same
|
|
218
|
+
mistake is a different failure shape — see the next section.
|
|
219
|
+
|
|
220
|
+
## An unresolvable type name in `# @rbs`
|
|
221
|
+
|
|
222
|
+
Naming a Rigor refinement (or any other unresolvable name) where an RBS type
|
|
223
|
+
belongs in the `# @rbs name: TYPE` tag form does not drop silently the way
|
|
224
|
+
`#:` does — it takes the whole class down:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
class ProbeZZ
|
|
228
|
+
# @rbs g: finite-float
|
|
229
|
+
def probe(g)
|
|
230
|
+
g.to_s
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Upstream's own type parser truncates `finite-float` to `finite` (a hyphen
|
|
236
|
+
cannot continue an RBS type name) before this ever reaches Rigor, so `finite`
|
|
237
|
+
is the only token that reaches `RBS::DefinitionBuilder` — and it names no
|
|
238
|
+
loaded type, so the build for the WHOLE class fails
|
|
239
|
+
(`RBS::NoTypeFoundError`). `probe`, and every other real method on `ProbeZZ`,
|
|
240
|
+
reads `Dynamic[top]`. This surfaces as
|
|
241
|
+
`rbs.coverage.definition-build-failed`, naming the token and, when it is the
|
|
242
|
+
truncated head of a registered refinement name, the `%a{rigor:v1:…}` spelling
|
|
243
|
+
that IS valid today (see
|
|
244
|
+
[RBS::Extended annotations](../16-rbs-extended-annotations.md)).
|
|
245
|
+
|
|
246
|
+
## Unparseable `# @rbs name:` types
|
|
247
|
+
|
|
248
|
+
A bounded or parameterised refinement — `Integer[1..10]`, `non-empty-array
|
|
249
|
+
[Integer]` — does not truncate at a hyphen the way `finite-float` does, so it
|
|
250
|
+
does not take the whole class down. In a `# @rbs name: TYPE` position it
|
|
251
|
+
leaves the parameter untyped instead, silently:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
class BoundedProbe
|
|
255
|
+
# @rbs n: Integer[1..10]
|
|
256
|
+
def probe(n)
|
|
257
|
+
n
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The gem's grammar makes the colon-and-type optional on this annotation, so a
|
|
263
|
+
`TYPE` it cannot parse leaves the parameter's name recorded and its type
|
|
264
|
+
unset rather than raising — indistinguishable downstream from a parameter
|
|
265
|
+
nobody annotated at all. Rigor reports the drop as
|
|
266
|
+
`plugin.rbs-inline.source-rbs-annotation-not-honoured`, naming the line and
|
|
267
|
+
the text that failed to parse, and points at the `%a{rigor:v1:param:}`
|
|
268
|
+
spelling that carries a refinement like `Integer[1..10]` correctly (see
|
|
269
|
+
[RBS::Extended annotations](../16-rbs-extended-annotations.md)) — the same
|
|
270
|
+
advice the `%a{rigor:v1:…}` pointer above gives for the tag form.
|
|
271
|
+
|
|
272
|
+
## An unrecognised tag after `@rbs`
|
|
273
|
+
|
|
274
|
+
The gem recognises a comment as an `@rbs` annotation attempt as soon as it
|
|
275
|
+
sees the word boundary right after `@rbs` — which matches a hyphen, not only
|
|
276
|
+
whitespace or end-of-line:
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
class TagProbe
|
|
280
|
+
# @rbs-ext return: non-empty-string
|
|
281
|
+
# @rbs return: String
|
|
282
|
+
def name
|
|
283
|
+
"x"
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`# @rbs-ext …` is inside that net, but nothing in the gem's grammar
|
|
289
|
+
recognises `-ext`, so the gem gives up on the whole paragraph and folds it
|
|
290
|
+
back into an ordinary comment — the neighbouring `# @rbs return: String`
|
|
291
|
+
line still binds, but the `@rbs-ext` line contributes nothing and, before
|
|
292
|
+
this, said nothing either. Rigor reports the drop as
|
|
293
|
+
`plugin.rbs-inline.source-rbs-annotation-not-honoured`, naming the line and
|
|
294
|
+
the comment text — without implying `@rbs-ext` is a recognised tag of any
|
|
295
|
+
kind. A comment that merely mentions `@rbs` in prose, or one that opens with
|
|
296
|
+
a tag not starting with `@rbs` (`# @extrbs …`), is outside the gem's
|
|
297
|
+
detector and stays silent.
|
|
298
|
+
|
|
112
299
|
## Configuration
|
|
113
300
|
|
|
114
301
|
```yaml
|
|
@@ -40,7 +40,7 @@ arguments.
|
|
|
40
40
|
| `plugin.sidekiq.worker-call` | info | a `Worker.perform_*` call matched a discovered worker's `#perform` arity |
|
|
41
41
|
| `plugin.sidekiq.wrong-arity` | error | the forwarded argument count falls outside `#perform`'s arity envelope (message names the schedule carve-out for `perform_in` / `perform_at`) |
|
|
42
42
|
| `plugin.sidekiq.missing-schedule` | error | `perform_in()` / `perform_at()` called with zero arguments (the schedule is required even when `#perform` takes none) |
|
|
43
|
-
| `plugin.sidekiq.load-error` | warning | worker discovery failed (parse/read error) — once per
|
|
43
|
+
| `plugin.sidekiq.load-error` | warning | worker discovery failed (parse/read error) — once per run, on `.rigor.yml` |
|
|
44
44
|
|
|
45
45
|
## What it types
|
|
46
46
|
|
|
@@ -144,6 +144,13 @@ added it is 100.
|
|
|
144
144
|
alternative `klass:` spelling, and a schedule built in Ruby with
|
|
145
145
|
`Sidekiq::Cron::Job.load_from_hash!`, supply no roots — the worker
|
|
146
146
|
stays a `rigor unused` candidate rather than being guessed at.
|
|
147
|
+
- **The "failed to discover workers" warning is run-scoped.** It is
|
|
148
|
+
a fact about your configuration, not about any one source file, so
|
|
149
|
+
it is reported once per run on `.rigor.yml` rather than repeated on
|
|
150
|
+
every analysed file. It is `:warning`, so if you baselined it at its
|
|
151
|
+
old position that entry no longer matches and the row fails a
|
|
152
|
+
`--fail-on=warning` run — regenerate with `rigor baseline
|
|
153
|
+
regenerate`.
|
|
147
154
|
|
|
148
155
|
## Plugin internals
|
|
149
156
|
|
|
@@ -47,11 +47,27 @@ plugins:
|
|
|
47
47
|
The plugin is **input-side only**: it translates Sorbet's syntax
|
|
48
48
|
into Rigor's type model. It does **not** run Sorbet's checker,
|
|
49
49
|
ship `sorbet-runtime`, or enforce Sorbet's runtime guarantees.
|
|
50
|
-
When an RBS sig and a Sorbet sig disagree,
|
|
51
|
-
sig
|
|
52
|
-
|
|
53
|
-
`T
|
|
54
|
-
documents the full vocabulary and
|
|
50
|
+
When an RBS sig and a Sorbet sig disagree, call sites get the
|
|
51
|
+
Sorbet sig's return type, and the method's body is checked
|
|
52
|
+
against the RBS return. Forms outside the
|
|
53
|
+
translation table (`T.proc`, `T.self_type`, …) degrade to
|
|
54
|
+
`Dynamic[top]`. Chapter 10 documents the full vocabulary and
|
|
55
|
+
these edges.
|
|
56
|
+
|
|
57
|
+
The plugin also types the annotation DSL's own expressions
|
|
58
|
+
(`sig`, `params`, `returns`, `void`, `abstract`, `override`,
|
|
59
|
+
`type_member`, `T::Array[...]`, `T::Helpers`/`T::Generic` macros,
|
|
60
|
+
`T::Struct`/`T::Enum` class bodies — `sig` on a plain
|
|
61
|
+
`T::Struct` subclass still needs the subclass's own `extend T::Sig`,
|
|
62
|
+
matching the runtime) through a bundled
|
|
63
|
+
`sorbet-runtime` RBS surface — so the sig DSL no longer reads as
|
|
64
|
+
`Dynamic[top]` on a Sorbet codebase. What it does not model:
|
|
65
|
+
`T::Struct` does not synthesise `prop`-declared readers (a
|
|
66
|
+
`Doc#name` accessor still reads opaque), `include T::Props` does
|
|
67
|
+
not confer the `ClassMethods` surface (only the `< T::Struct`
|
|
68
|
+
subclass path does), and the deeper type-member machinery
|
|
69
|
+
(`T.attached_class` variance, `type_member` bounds) is not
|
|
70
|
+
propagated into call sites.
|
|
55
71
|
|
|
56
72
|
## Plugin internals
|
|
57
73
|
|
data/exe/rigor
CHANGED
|
@@ -11,13 +11,28 @@
|
|
|
11
11
|
# context across the exec. The `none` and `process` (default) strategies
|
|
12
12
|
# need no flag, so this is a no-op for them — behaviour is unchanged
|
|
13
13
|
# unless a project opts into `ruby_box`.
|
|
14
|
+
#
|
|
15
|
+
# Before re-exec'ing, a child Ruby probes whether this Ruby can run
|
|
16
|
+
# Rigor under `RUBY_BOX=1` at all (Rigor::Plugin::BoxProbe — released
|
|
17
|
+
# Rubies through 4.0.7 segfault on Ruby Bug #22260). When it cannot,
|
|
18
|
+
# the request is dropped with a warning and the run proceeds under the
|
|
19
|
+
# configured strategy (`process` by default) instead of crashing.
|
|
14
20
|
rigor_isolation = ENV["RIGOR_PLUGIN_ISOLATION"].to_s
|
|
15
21
|
rigor_wants_box = rigor_isolation == "ruby_box" || !ENV["RIGOR_BOX"].to_s.empty?
|
|
16
22
|
if rigor_wants_box && ENV["RUBY_BOX"].to_s.empty?
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
require_relative "../lib/rigor/plugin/box_probe"
|
|
24
|
+
if (rigor_box_unsupported = Rigor::Plugin::BoxProbe.unsupported_reason)
|
|
25
|
+
rigor_box_warning = "rigor: ruby_box plugin isolation is unavailable: #{rigor_box_unsupported}. " \
|
|
26
|
+
"Falling back to the configured plugins_isolation strategy (default: process)."
|
|
27
|
+
# `$stderr.puts`, not `warn`: `-W0` silences `warn`, and this fallback must never be silent.
|
|
28
|
+
$stderr.puts rigor_box_warning # rubocop:disable Style/StderrPuts
|
|
29
|
+
ENV.delete("RIGOR_PLUGIN_ISOLATION") if rigor_isolation == "ruby_box"
|
|
30
|
+
ENV.delete("RIGOR_BOX")
|
|
31
|
+
else
|
|
32
|
+
ENV["RUBY_BOX"] = "1"
|
|
33
|
+
ENV["RIGOR_PLUGIN_ISOLATION"] = "ruby_box" if rigor_isolation.empty?
|
|
34
|
+
exec(RbConfig.ruby, __FILE__, *ARGV)
|
|
35
|
+
end
|
|
21
36
|
end
|
|
22
37
|
|
|
23
38
|
lib = File.expand_path("../lib", __dir__)
|
|
@@ -32,9 +32,19 @@ module Rigor
|
|
|
32
32
|
# `docs/internal-spec/inference-engine.md` § "Declaration-sourced provenance mark (ADR-58)"; keep this
|
|
33
33
|
# comment subordinate to it.
|
|
34
34
|
#
|
|
35
|
-
# So `marked?` matches exactly the
|
|
36
|
-
#
|
|
35
|
+
# So `marked?` matches exactly the node shapes the scope actually models and invents no propagation of its
|
|
36
|
+
# own; anything flow-live keeps firing.
|
|
37
|
+
#
|
|
38
|
+
# Issue #1362 adds a third carrier, a global still on its program-global seed, which joins the non-nil part of
|
|
39
|
+
# the declared type of a global Ruby's own signatures declare with the file's writes (ADR-117 Decision point
|
|
40
|
+
# 2: never warn that a value might deviate from the idiom). Its declared members are the declaration-sourced
|
|
41
|
+
# part. The consumers judge such a value by the file's writes ({.written_type}, {.withholds_nil?}), and a
|
|
42
|
+
# value that mixes it with something else without the declared members the file never writes
|
|
43
|
+
# ({.declared_only_rejection?}), so a report the writes alone earn still fires.
|
|
37
44
|
module DeclarationSourcedGuard
|
|
45
|
+
NO_GLOBALS = [].freeze
|
|
46
|
+
private_constant :NO_GLOBALS
|
|
47
|
+
|
|
38
48
|
module_function
|
|
39
49
|
|
|
40
50
|
# True when `node` is a direct read of a binding whose optionality is purely declaration-sourced. Any
|
|
@@ -44,9 +54,164 @@ module Rigor
|
|
|
44
54
|
case node
|
|
45
55
|
when Prism::InstanceVariableReadNode then scope.declaration_sourced?(:ivar, node.name)
|
|
46
56
|
when Prism::LocalVariableReadNode then scope.declaration_sourced?(:local, node.name)
|
|
57
|
+
when Prism::GlobalVariableReadNode then scope.declaration_sourced?(:global, node.name)
|
|
47
58
|
else false
|
|
48
59
|
end
|
|
49
60
|
end
|
|
61
|
+
|
|
62
|
+
# The globals whose declared seeds `node` still reads, empty for none: a read of a marked global, or of a
|
|
63
|
+
# local copied from one or more (`sep = $/`, `Scope#declaration_sourced_global_copies`), bare or in
|
|
64
|
+
# parentheses (`($stdout)`).
|
|
65
|
+
def global_sources(node, scope)
|
|
66
|
+
node = unparenthesised(node)
|
|
67
|
+
case node
|
|
68
|
+
when Prism::GlobalVariableReadNode
|
|
69
|
+
scope.declaration_sourced?(:global, node.name) ? [node.name] : NO_GLOBALS
|
|
70
|
+
when Prism::LocalVariableReadNode
|
|
71
|
+
scope.declaration_sourced_global_copies(node.name)
|
|
72
|
+
else NO_GLOBALS
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The globals a local written from `value` copies the declared seeds of, empty for none: a read of a marked
|
|
77
|
+
# global, bare or in parentheses (`sep = ($/)`), or a bare read of a local that copies some (`s = sep`).
|
|
78
|
+
# Unlike an ivar copy's, this mark follows bare local-to-local copies; a method result (`$/.dup`) or a
|
|
79
|
+
# container element (`[$/].each { |s| … }`) carries none, as ADR-58's one-hop boundary has it.
|
|
80
|
+
def copied_globals(value, scope)
|
|
81
|
+
return scope.declaration_sourced_global_copies(value.name) if value.is_a?(Prism::LocalVariableReadNode)
|
|
82
|
+
|
|
83
|
+
read = unparenthesised(value)
|
|
84
|
+
return NO_GLOBALS unless read.is_a?(Prism::GlobalVariableReadNode) &&
|
|
85
|
+
scope.declaration_sourced?(:global, read.name)
|
|
86
|
+
|
|
87
|
+
[read.name]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# `node` with its enclosing parentheses taken off while they hold a single expression.
|
|
91
|
+
def unparenthesised(node)
|
|
92
|
+
while node.is_a?(Prism::ParenthesesNode)
|
|
93
|
+
body = node.body
|
|
94
|
+
break unless body.is_a?(Prism::StatementsNode) && body.body.size == 1
|
|
95
|
+
|
|
96
|
+
node = body.body.first
|
|
97
|
+
end
|
|
98
|
+
node
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The union of the file's own writes to the globals `node` reads the seeds of ({.global_sources}), or nil.
|
|
102
|
+
def written_type(node, scope)
|
|
103
|
+
sources = global_sources(node, scope)
|
|
104
|
+
return nil if sources.empty?
|
|
105
|
+
|
|
106
|
+
written = sources.map { |source| scope.program_globals[source] }
|
|
107
|
+
written.include?(nil) ? nil : Type::Combinator.union(*written)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# True when a `nil` in `node`'s type is not diagnostic fuel: for a global's seed or its copy, when the file
|
|
111
|
+
# never writes `nil` to the global, so the declaration alone contributes it; otherwise when `node` carries
|
|
112
|
+
# the ADR-58 mark.
|
|
113
|
+
def withholds_nil?(node, scope)
|
|
114
|
+
written = written_type(node, scope)
|
|
115
|
+
return marked?(node, scope) if written.nil?
|
|
116
|
+
|
|
117
|
+
!nil_bearing?(written)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Issue #1362 — true when every member of `type` that `expected` rejects is a declared member of a builtin
|
|
121
|
+
# global this file writes and joins ({#declared_only_members}), so the rejection rests on the join alone. The
|
|
122
|
+
# argument and return gates read it for a value that mixes such a global with something else
|
|
123
|
+
# (`c ? $stdout : StringIO.new`), which the ADR-58 mark does not follow. It is a type-level reading: it also
|
|
124
|
+
# withholds a rejection of such a member that comes from elsewhere in the file (`STDOUT` passed where a
|
|
125
|
+
# `StringIO` is required, in a file that writes `$stdout`).
|
|
126
|
+
#
|
|
127
|
+
# Otherwise the value with those members taken out of every union inside it ({#without_declared_only}) is
|
|
128
|
+
# handed to the block, which answers whether the caller's rule accepts it: the value as it was before the
|
|
129
|
+
# join, one level into a container it builds as well (`[$stdout]` or `{ io: $stdout }` passed where an
|
|
130
|
+
# `Array[StringIO]` is required), and with a `nil` the file writes judged as the rule judges a `nil`.
|
|
131
|
+
def declared_only_rejection?(type, expected, scope)
|
|
132
|
+
declared_only = declared_only_members(scope)
|
|
133
|
+
return false if declared_only.empty?
|
|
134
|
+
|
|
135
|
+
members = type.is_a?(Type::Union) ? type.members : [type]
|
|
136
|
+
rejected = members.select { |member| Inference::Acceptance.accepts(expected, member, mode: :gradual).no? }
|
|
137
|
+
return false if rejected.empty?
|
|
138
|
+
return true if rejected.all? { |member| declared_only.include?(member) }
|
|
139
|
+
|
|
140
|
+
stripped = without_declared_only(type, declared_only)
|
|
141
|
+
!stripped.equal?(type) && yield(stripped)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# `type` with each member of `declared_only` taken out of every union in it, through tuples, hash shapes and
|
|
145
|
+
# generic arguments; a union with nothing else left keeps its members. Answers `type` itself when nothing
|
|
146
|
+
# was taken out.
|
|
147
|
+
def without_declared_only(type, declared_only)
|
|
148
|
+
case type
|
|
149
|
+
when Type::Union then union_without_declared_only(type, declared_only)
|
|
150
|
+
when Type::Tuple
|
|
151
|
+
elements = type.elements.map { |element| without_declared_only(element, declared_only) }
|
|
152
|
+
elements == type.elements ? type : Type::Combinator.tuple_of(*elements)
|
|
153
|
+
when Type::HashShape
|
|
154
|
+
pairs = type.pairs.transform_values { |value| without_declared_only(value, declared_only) }
|
|
155
|
+
pairs == type.pairs ? type : rebuild_hash_shape(type, pairs)
|
|
156
|
+
when Type::Nominal
|
|
157
|
+
args = type.type_args.map { |arg| without_declared_only(arg, declared_only) }
|
|
158
|
+
args == type.type_args ? type : Type::Combinator.nominal_of(type.class_name, type_args: args)
|
|
159
|
+
else type
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def union_without_declared_only(union, declared_only)
|
|
164
|
+
kept = union.members.reject { |member| declared_only.include?(member) }
|
|
165
|
+
kept = union.members if kept.empty?
|
|
166
|
+
rebuilt = kept.map { |member| without_declared_only(member, declared_only) }
|
|
167
|
+
rebuilt == union.members ? union : Type::Combinator.union(*rebuilt)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def rebuild_hash_shape(shape, pairs)
|
|
171
|
+
Type::Combinator.hash_shape_of(
|
|
172
|
+
pairs, required_keys: shape.required_keys, optional_keys: shape.optional_keys,
|
|
173
|
+
read_only_keys: shape.read_only_keys, extra_keys: shape.extra_keys
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# For each builtin global this file writes and joins, the non-nil members of its declared type that the file's
|
|
178
|
+
# writes to that global do not hold (`IO` after `$stdout = StringIO.new`, nothing after `$stderr = STDERR`),
|
|
179
|
+
# gathered over the globals. A declared literal also counts in its class form, which a method called on the
|
|
180
|
+
# joined union returns: `$VERBOSE.itself` reads `FalseClass | TrueClass` after `$VERBOSE = true`.
|
|
181
|
+
def declared_only_members(scope)
|
|
182
|
+
seeds = scope.discovery.program_global_seeds
|
|
183
|
+
return NO_GLOBALS if seeds.empty?
|
|
184
|
+
|
|
185
|
+
seeds.keys.flat_map do |name|
|
|
186
|
+
written = type_members(scope.program_globals[name])
|
|
187
|
+
type_members(scope.environment.global_for_name(name, builtin: true))
|
|
188
|
+
.reject { |member| nil_bearing?(member) }
|
|
189
|
+
.flat_map { |member| [member, *class_form(member)] }
|
|
190
|
+
.reject { |member| written.include?(member) }
|
|
191
|
+
end.uniq
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def class_form(member)
|
|
195
|
+
return NO_GLOBALS unless member.is_a?(Type::Constant)
|
|
196
|
+
|
|
197
|
+
[Type::Combinator.nominal_of(member.value.class.name)]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def type_members(type)
|
|
201
|
+
return NO_GLOBALS if type.nil?
|
|
202
|
+
|
|
203
|
+
type.is_a?(Type::Union) ? type.members : [type]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def nil_bearing?(type)
|
|
207
|
+
members = type.is_a?(Type::Union) ? type.members : [type]
|
|
208
|
+
members.any? do |member|
|
|
209
|
+
(member.is_a?(Type::Constant) && member.value.nil?) ||
|
|
210
|
+
(member.is_a?(Type::Nominal) && member.class_name == "NilClass")
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
private_class_method :nil_bearing?, :unparenthesised, :declared_only_members, :type_members, :class_form,
|
|
214
|
+
:without_declared_only, :union_without_declared_only, :rebuild_hash_shape
|
|
50
215
|
end
|
|
51
216
|
end
|
|
52
217
|
end
|