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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../check_rules/rule_ids"
|
|
3
4
|
require_relative "../crash_signature"
|
|
4
5
|
require_relative "../diagnostic"
|
|
5
6
|
require_relative "../severity_stamp"
|
|
@@ -39,7 +40,9 @@ module Rigor
|
|
|
39
40
|
# (`[error_class, first_error_line, conflicting_buffer_names]`) or nil when the env built.
|
|
40
41
|
# @param definition_build_failures_snapshot — issue #696 — reader returning the per-class
|
|
41
42
|
# `RBS::DefinitionBuilder` failures the run observed, as `[class_name, error_class, member,
|
|
42
|
-
# conflicting_buffer_names]` tuples
|
|
43
|
+
# conflicting_buffer_names, unresolved_type_name_detail]` tuples (issue #997 added the fifth
|
|
44
|
+
# element — `[token, location, refinement_hint]`, `nil` unless `error_class` is
|
|
45
|
+
# `RBS::NoTypeFoundError`). Empty for a healthy sig set.
|
|
43
46
|
# @param hkt_scan_failure_snapshot — issue #784 — reader returning the `[error_class_name,
|
|
44
47
|
# first_message_line, raw_frame_or_nil, stage]` tuple whichever stage of the HKT-registry build
|
|
45
48
|
# raised, or nil when both built (or were never demanded). `stage` is `:scan` (the RBS `type`-alias
|
|
@@ -51,7 +54,8 @@ module Rigor
|
|
|
51
54
|
pre_eval_diagnostics_from_scanner:, synthesized_namespaces_snapshot:,
|
|
52
55
|
quarantined_signatures_snapshot:, env_build_failure_snapshot:,
|
|
53
56
|
definition_build_failures_snapshot:, hkt_scan_failure_snapshot:,
|
|
54
|
-
conformance_results_snapshot:, signature_standdowns_snapshot: -> { [] }
|
|
57
|
+
conformance_results_snapshot:, signature_standdowns_snapshot: -> { [] },
|
|
58
|
+
pooled_run_disclosures: -> { [] })
|
|
55
59
|
@configuration = configuration
|
|
56
60
|
@rbs_extended_reporter = rbs_extended_reporter
|
|
57
61
|
@boundary_cross_reporter = boundary_cross_reporter
|
|
@@ -68,6 +72,9 @@ module Rigor
|
|
|
68
72
|
@definition_build_failures_snapshot_reader = definition_build_failures_snapshot
|
|
69
73
|
@hkt_scan_failure_snapshot_reader = hkt_scan_failure_snapshot
|
|
70
74
|
@conformance_results_snapshot_reader = conformance_results_snapshot
|
|
75
|
+
# Issue #1051 — reader for the pool coordinator's de-duplicated run-scoped disclosure table.
|
|
76
|
+
# Empty on a sequential run, where `plugin_registry`'s own instances are the ones that registered.
|
|
77
|
+
@pooled_run_disclosures_reader = pooled_run_disclosures
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
# Pre-file diagnostic streams that fire once per run rather than per analyzed file: plugin load /
|
|
@@ -441,8 +448,9 @@ module Rigor
|
|
|
441
448
|
# "Explicit conformance directive"). A class that declares `conforms-to _Interface` but is missing
|
|
442
449
|
# a required interface method surfaces as `rbs_extended.unsatisfied-conformance`; an unresolvable
|
|
443
450
|
# interface name surfaces as `dynamic.rbs-extended.unresolved` `:info` (the same fail-soft channel
|
|
444
|
-
# the other directive parsers use). Empty for a project with no directive
|
|
445
|
-
# conformance, or
|
|
451
|
+
# the other directive parsers use). Empty for a project with no directive or a well-formed
|
|
452
|
+
# conformance; every analysis path, pooled or degraded, takes the snapshot `synthesized_namespaces`
|
|
453
|
+
# comes from.
|
|
446
454
|
def conforms_to_diagnostics
|
|
447
455
|
results = conformance_results_snapshot
|
|
448
456
|
return [] if results.nil? || results.empty?
|
|
@@ -585,22 +593,50 @@ module Rigor
|
|
|
585
593
|
end
|
|
586
594
|
|
|
587
595
|
# The closing advice follows the FIRST failure's error class: `GenericParameterMismatchError` is two
|
|
588
|
-
# declarations of one CLASS at different generic arity (#610)
|
|
589
|
-
#
|
|
596
|
+
# declarations of one CLASS at different generic arity (#610); `NoTypeFoundError` (issue #997) is
|
|
597
|
+
# ONE declaration naming a type that does not exist, which "remove the duplicate declaration" sends
|
|
598
|
+
# its reader after a duplicate that is not there — the fifth tuple element
|
|
599
|
+
# ({RbsLoader#unresolved_type_name_detail}) is non-nil for exactly that case. Every other error class
|
|
600
|
+
# (the two duplicated-definition errors, `RecursiveAncestorError`, …) keeps the duplicate-declaration
|
|
601
|
+
# wording, which is accurate for them.
|
|
590
602
|
def definition_build_advice(failure)
|
|
591
|
-
_, error_class, = failure
|
|
603
|
+
_, error_class, _member, _buffers, unresolved = failure
|
|
592
604
|
if error_class.to_s.end_with?("GenericParameterMismatchError")
|
|
593
605
|
"Two signature sources declare the class with a different number of type parameters; make " \
|
|
594
606
|
"the declarations agree (`rbs validate`) to restore type coverage."
|
|
607
|
+
elsif unresolved
|
|
608
|
+
unresolved_type_name_advice(unresolved)
|
|
595
609
|
else
|
|
596
610
|
"Two signature sources declare the same member; remove the duplicate declaration " \
|
|
597
611
|
"(`rbs validate`) to restore type coverage."
|
|
598
612
|
end
|
|
599
613
|
end
|
|
600
614
|
|
|
601
|
-
#
|
|
602
|
-
#
|
|
603
|
-
#
|
|
615
|
+
# Issue #997 — `unresolved` is {RbsLoader#unresolved_type_name_detail}'s `[token, location,
|
|
616
|
+
# refinement_hint]`. `refinement_hint` turns the message into a fix: the token is not a typo to
|
|
617
|
+
# chase down but the start of a Rigor refinement name that is not valid in an RBS type position, and
|
|
618
|
+
# the reader is told the spelling that IS valid today. Without a hint, the token is named with its
|
|
619
|
+
# position and the reader is left with the same "not a duplicate" correction plus a typo/missing-load
|
|
620
|
+
# check — still narrower than the duplicate-declaration wording it replaces.
|
|
621
|
+
def unresolved_type_name_advice(unresolved)
|
|
622
|
+
token, location, refinement_hint = unresolved
|
|
623
|
+
position = location ? " at `#{location}`" : ""
|
|
624
|
+
if refinement_hint
|
|
625
|
+
"`#{token}`#{position} does not name a type RBS can resolve; it is the start of the Rigor " \
|
|
626
|
+
"refinement `#{refinement_hint}`, which is not valid in an RBS type position. Write it as " \
|
|
627
|
+
"an `%a{rigor:v1:param: name is #{refinement_hint}}` (or `return:` / `assert`) annotation " \
|
|
628
|
+
"alongside the plain RBS type instead — see docs/manual/16-rbs-extended-annotations.md — " \
|
|
629
|
+
"to restore type coverage."
|
|
630
|
+
else
|
|
631
|
+
"One signature source names `#{token}`#{position}, which is not a type RBS can resolve; fix " \
|
|
632
|
+
"the typo, or if it names a class, confirm it is loaded (`rbs validate`) to restore type " \
|
|
633
|
+
"coverage."
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
# `[class_name, error_class, member, buffers, unresolved_type_name_detail]`. The member is nil for
|
|
638
|
+
# the error classes that carry no name at all (`RecursiveAncestorError`), and the clause then names
|
|
639
|
+
# the error class alone rather than inventing a member.
|
|
604
640
|
def first_failure_clause(failure)
|
|
605
641
|
_, error_class, member, = failure
|
|
606
642
|
return " First failure: #{error_class}." if member.nil? || member.empty?
|
|
@@ -849,10 +885,29 @@ module Rigor
|
|
|
849
885
|
return [] if @source_rbs_synthesis_reporter.empty?
|
|
850
886
|
|
|
851
887
|
@source_rbs_synthesis_reporter.entries.map do |entry|
|
|
852
|
-
entry.kind
|
|
888
|
+
case entry.kind
|
|
889
|
+
when :not_honoured then not_honoured_diagnostic(entry)
|
|
890
|
+
when :contradiction then contradicting_signature_diagnostic(entry)
|
|
891
|
+
else synthesis_failed_diagnostic(entry)
|
|
892
|
+
end
|
|
853
893
|
end
|
|
854
894
|
end
|
|
855
895
|
|
|
896
|
+
# ADR-112 WD5 / issue #1075 — a member whose two declarations contradict, or whose refinement is
|
|
897
|
+
# outside its own declared type. An error in every profile: the false-positive discipline is carried
|
|
898
|
+
# by the rule's narrow definition of a contradiction, never by the severity (ADR-112 WD5). The message
|
|
899
|
+
# is composed where the record is read ({Environment.record_member_consistency}), since only there
|
|
900
|
+
# are both sources' paths at hand.
|
|
901
|
+
def contradicting_signature_diagnostic(entry)
|
|
902
|
+
Diagnostic.new(
|
|
903
|
+
path: entry.path, line: entry.line || 1, column: 1,
|
|
904
|
+
message: entry.message,
|
|
905
|
+
severity: :error,
|
|
906
|
+
rule: CheckRules::RULE_CONTRADICTING_SIGNATURE,
|
|
907
|
+
source_family: :builtin
|
|
908
|
+
)
|
|
909
|
+
end
|
|
910
|
+
|
|
856
911
|
def synthesis_failed_diagnostic(entry)
|
|
857
912
|
Diagnostic.new(
|
|
858
913
|
path: entry.path, line: 1, column: 1,
|
|
@@ -938,6 +993,103 @@ module Rigor
|
|
|
938
993
|
plugin_registry.plugins.filter_map { |plugin| plugin_trust_refusal_diagnostic(plugin) }
|
|
939
994
|
end
|
|
940
995
|
|
|
996
|
+
# Issue #1051 — the run-scoped plugin disclosures ({Plugin::Base#disclose_once}), the run's
|
|
997
|
+
# project-global notices about its own inputs. Two sources feed one table:
|
|
998
|
+
#
|
|
999
|
+
# - the coordinator-side registry, which is where a SEQUENTIAL run's `#prepare` and per-file hooks
|
|
1000
|
+
# ran; and
|
|
1001
|
+
# - `pooled_run_disclosures`, the pool coordinator's already-de-duplicated collection from the
|
|
1002
|
+
# pre-fork parent session and every worker payload.
|
|
1003
|
+
#
|
|
1004
|
+
# Both are keyed `[plugin id, key]` and the first registration of a pair wins, so a run emits one
|
|
1005
|
+
# row per pair no matter how many plugin instances (workers) reached it — the #1051 bug, where a
|
|
1006
|
+
# per-instance `@emitted` flag produced one row per WORKER.
|
|
1007
|
+
#
|
|
1008
|
+
# The emitted order is `(registry position, key)`, NOT registration order: a worker's slice
|
|
1009
|
+
# determines which instance registers a key first, so registration order is a function of
|
|
1010
|
+
# `--workers N` and would make a pooled run's stream differ from a sequential one's even with the
|
|
1011
|
+
# duplicates gone. Registry position is the plugin load order (topological by `consumes:`), which
|
|
1012
|
+
# both modes share.
|
|
1013
|
+
#
|
|
1014
|
+
# Position is `.rigor.yml:1:1` for every row; see {Plugin::Base#disclose_once} for why that and not
|
|
1015
|
+
# the first analysed file. A positioned batch ({Plugin::Base#emit_once}) shares the table but not
|
|
1016
|
+
# this stream — see {#plugin_run_emission_diagnostics}.
|
|
1017
|
+
def plugin_run_disclosure_diagnostics
|
|
1018
|
+
ordered_run_disclosure_records
|
|
1019
|
+
.reject { |record| record.key?(:diagnostics) }
|
|
1020
|
+
.map { |record| run_disclosure_diagnostic(record) }
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
# Issue #1060 — the positioned batches ({Plugin::Base#emit_once}) from the same table, de-duplicated
|
|
1024
|
+
# by the same `[plugin id, key]` identity over the same sources, so the first registration of a key
|
|
1025
|
+
# wins whole and a batch is never merged row by row across workers. Unlike a disclosure, every row
|
|
1026
|
+
# keeps the position the plugin gave it; the engine only stamps `source_family: "plugin.<id>"`, the
|
|
1027
|
+
# same stamp a `#diagnostics_for_file` row gets, so a baseline keyed to the row's own file and
|
|
1028
|
+
# qualified rule matches it before and after a plugin moves onto this channel.
|
|
1029
|
+
#
|
|
1030
|
+
# Order is `(registry position, key)` and then the batch's own row order — the same
|
|
1031
|
+
# `--workers`-independent order the disclosure stream uses. The runner appends this stream right
|
|
1032
|
+
# after the per-file stream rather than into the run-level block: these rows are about files, and
|
|
1033
|
+
# the files they name are usually not among the analysed targets (a view template), so there is no
|
|
1034
|
+
# file slot to splice them into.
|
|
1035
|
+
def plugin_run_emission_diagnostics
|
|
1036
|
+
ordered_run_disclosure_records
|
|
1037
|
+
.select { |record| record.key?(:diagnostics) }
|
|
1038
|
+
.flat_map { |record| record[:diagnostics].map { |row| run_emission_diagnostic(row, record[:plugin_id]) } }
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
def ordered_run_disclosure_records
|
|
1042
|
+
registry = plugin_registry
|
|
1043
|
+
return [] if registry.empty?
|
|
1044
|
+
|
|
1045
|
+
positions = {}
|
|
1046
|
+
registry.plugins.each_with_index { |plugin, index| positions[disclosure_plugin_id(plugin)] ||= index }
|
|
1047
|
+
collect_run_disclosure_records(registry)
|
|
1048
|
+
.sort_by { |record| [positions.fetch(record[:plugin_id], positions.size), record[:key].to_s] }
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
def collect_run_disclosure_records(registry)
|
|
1052
|
+
records = {}
|
|
1053
|
+
Array(pooled_run_disclosures).each do |record|
|
|
1054
|
+
records[[record[:plugin_id], record[:key]]] ||= record
|
|
1055
|
+
end
|
|
1056
|
+
registry.plugins.each do |plugin|
|
|
1057
|
+
plugin_id = disclosure_plugin_id(plugin)
|
|
1058
|
+
Array(plugin.run_disclosure_records).each do |record|
|
|
1059
|
+
records[[plugin_id, record[:key]]] ||= record.merge(plugin_id: plugin_id)
|
|
1060
|
+
end
|
|
1061
|
+
rescue StandardError
|
|
1062
|
+
next
|
|
1063
|
+
end
|
|
1064
|
+
records.values
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
def run_disclosure_diagnostic(record)
|
|
1068
|
+
Diagnostic.new(
|
|
1069
|
+
path: ".rigor.yml", line: 1, column: 1,
|
|
1070
|
+
message: record.fetch(:message),
|
|
1071
|
+
severity: record.fetch(:severity),
|
|
1072
|
+
rule: record.fetch(:rule),
|
|
1073
|
+
source_family: "plugin.#{record[:plugin_id]}"
|
|
1074
|
+
)
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
def run_emission_diagnostic(row, plugin_id)
|
|
1078
|
+
Diagnostic.new(
|
|
1079
|
+
path: row.path, line: row.line, column: row.column,
|
|
1080
|
+
message: row.message,
|
|
1081
|
+
severity: row.severity,
|
|
1082
|
+
rule: row.rule,
|
|
1083
|
+
source_family: "plugin.#{plugin_id}"
|
|
1084
|
+
)
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
def disclosure_plugin_id(plugin)
|
|
1088
|
+
plugin.manifest.id
|
|
1089
|
+
rescue StandardError
|
|
1090
|
+
plugin.class.to_s
|
|
1091
|
+
end
|
|
1092
|
+
|
|
941
1093
|
def plugin_trust_refusal_diagnostic(plugin)
|
|
942
1094
|
summary = plugin.io_boundary.refusal_summary
|
|
943
1095
|
return nil if summary.nil?
|
|
@@ -1024,6 +1176,10 @@ module Rigor
|
|
|
1024
1176
|
@cached_plugin_prepare_diagnostics_reader.call
|
|
1025
1177
|
end
|
|
1026
1178
|
|
|
1179
|
+
def pooled_run_disclosures
|
|
1180
|
+
@pooled_run_disclosures_reader.call
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1027
1183
|
def pre_eval_diagnostics_from_scanner
|
|
1028
1184
|
@pre_eval_diagnostics_from_scanner_reader.call
|
|
1029
1185
|
end
|
|
@@ -250,9 +250,7 @@ module Rigor
|
|
|
250
250
|
)
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
-
def table_class_names
|
|
254
|
-
@effect_table.keys.filter_map { |key| Effects::MethodKey.owner(key) }.uniq
|
|
255
|
-
end
|
|
253
|
+
def table_class_names = @effect_table.keys.filter_map { |k| Effects::MethodKey.envelope_owner(k) }.uniq
|
|
256
254
|
|
|
257
255
|
def judge(scan)
|
|
258
256
|
Effects::EnvelopeCheck.run(
|
|
@@ -301,7 +299,7 @@ module Rigor
|
|
|
301
299
|
|
|
302
300
|
def build_diagnostic(finding)
|
|
303
301
|
Diagnostic.new(
|
|
304
|
-
path: finding.path || ".rigor.yml",
|
|
302
|
+
path: finding.path || unit_source_path(finding.key) || ".rigor.yml",
|
|
305
303
|
line: finding.line,
|
|
306
304
|
column: 1,
|
|
307
305
|
message: message_for(finding),
|
|
@@ -312,6 +310,13 @@ module Rigor
|
|
|
312
310
|
)
|
|
313
311
|
end
|
|
314
312
|
|
|
313
|
+
# #393 — a template unit has no `def` for {Effects::EnvelopeCheck::Positions} to find, so a view
|
|
314
|
+
# finding would land on `.rigor.yml` — the config file, not the template the reviewer has to open.
|
|
315
|
+
# `Runner#effect_sources` already records the file every unit key came from; the line stays at the
|
|
316
|
+
# finding's own (1 for a whole-file unit), because the bound is exceeded by the unit, not by one
|
|
317
|
+
# line of it, and the explanation chain names the origin site.
|
|
318
|
+
def unit_source_path(key) = Effects::MethodKey.template_unit?(key) ? @unit_sources[key.to_s]&.first : nil
|
|
319
|
+
|
|
315
320
|
def message_for(finding)
|
|
316
321
|
EnvelopeMessages.exceeded(finding)
|
|
317
322
|
end
|
|
@@ -10,6 +10,7 @@ require_relative "../run_stats"
|
|
|
10
10
|
require_relative "../../effects/signature_sources"
|
|
11
11
|
require_relative "../../rbs_extended/conformance_checker"
|
|
12
12
|
require_relative "../../runtime/jit"
|
|
13
|
+
require_relative "../../cache/engine_source"
|
|
13
14
|
|
|
14
15
|
module Rigor
|
|
15
16
|
module Analysis
|
|
@@ -44,7 +45,7 @@ module Rigor
|
|
|
44
45
|
snapshots:, plugin_registry:, dependency_source_index:,
|
|
45
46
|
synthetic_method_index:, project_patched_methods:,
|
|
46
47
|
analyze_file:, project_scope_seed: -> { {} }, record_dependencies: false,
|
|
47
|
-
restored_run_level_rows: nil)
|
|
48
|
+
restored_run_level_rows: nil, template_units: -> { TemplateUnits.empty })
|
|
48
49
|
@configuration = configuration
|
|
49
50
|
@cache_store = cache_store
|
|
50
51
|
@explain = explain
|
|
@@ -59,6 +60,11 @@ module Rigor
|
|
|
59
60
|
# `#collected_effects`, so a pooled run's effect graph is the sequential run's graph.
|
|
60
61
|
@record_effects = configuration.effects_enabled?
|
|
61
62
|
@collected_effects = {}
|
|
63
|
+
# Issue #1051 — run-scoped plugin disclosures ({Plugin::Base#disclose_once}), keyed
|
|
64
|
+
# `[plugin id, key]` so the FIRST worker to register a pair wins and every later copy is dropped.
|
|
65
|
+
# The de-duplication has to happen here, on the parent, because each fork worker owns its own
|
|
66
|
+
# plugin instances and therefore its own `@run_disclosures` table.
|
|
67
|
+
@collected_run_disclosures = {}
|
|
62
68
|
@collect_stats = collect_stats
|
|
63
69
|
@buffer = buffer
|
|
64
70
|
@environment_override = environment_override
|
|
@@ -71,6 +77,10 @@ module Rigor
|
|
|
71
77
|
@synthetic_method_index_reader = synthetic_method_index
|
|
72
78
|
@project_patched_methods_reader = project_patched_methods
|
|
73
79
|
@project_scope_seed_reader = project_scope_seed
|
|
80
|
+
# #392 — read through a proc, like every other runner-owned table here: the units are synthesised
|
|
81
|
+
# on the parent during the run and handed to the ONE pre-fork {WorkerSession}, so every child
|
|
82
|
+
# inherits the same compiled bytes.
|
|
83
|
+
@template_units_reader = template_units
|
|
74
84
|
@analyze_file = analyze_file
|
|
75
85
|
# Issues #796 / #794 — the previous full run's run-level rows, handed down by
|
|
76
86
|
# {Analysis::IncrementalSession} on a NARROWED run only (a recheck's closure, a
|
|
@@ -88,6 +98,25 @@ module Rigor
|
|
|
88
98
|
# these with its own sequential records before the post-pool fixpoint.
|
|
89
99
|
attr_reader :collected_effects
|
|
90
100
|
|
|
101
|
+
# Issue #1051 — the run-scoped disclosures a pool run collected, de-duplicated by `[plugin id, key]`
|
|
102
|
+
# and in first-registration order. Empty on a sequential run: there the runner's own plugin
|
|
103
|
+
# registry is the one that registered them, and {Runner} reads it directly.
|
|
104
|
+
def collected_run_disclosures
|
|
105
|
+
@collected_run_disclosures.values
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Issue #1051 — folds one session's / one worker payload's disclosures in, keeping the first
|
|
109
|
+
# registration of each `[plugin id, key]` pair. Nothing here inspects the message: two workers that
|
|
110
|
+
# disclose the same key with differently interpolated text still collapse to one row, which is the
|
|
111
|
+
# point — the key is the identity a plugin author controls, and an author who interpolates
|
|
112
|
+
# worker-varying text into a message has authored one disclosure, not N.
|
|
113
|
+
def merge_run_disclosures(records)
|
|
114
|
+
Array(records).each do |record|
|
|
115
|
+
identity = [record[:plugin_id], record[:key]].freeze
|
|
116
|
+
@collected_run_disclosures[identity] ||= record
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
91
120
|
# ADR-15 Phase 4b — pool mode is enabled when `@workers > 0`. Editor mode (`buffer:` non-nil)
|
|
92
121
|
# silently overrides pool mode to sequential: per design § "Ractor pool mode", the pool's warm-up
|
|
93
122
|
# cost dominates one-file wall time, so the pool gains nothing on a per-buffer invocation. The
|
|
@@ -474,6 +503,20 @@ module Rigor
|
|
|
474
503
|
# (shareable) registry into the class-ivar cache before any worker reads.
|
|
475
504
|
Environment::ClassRegistry.default
|
|
476
505
|
|
|
506
|
+
# #1055 — the same treatment for the engine-source digest. Every worker's `Environment.for_project`
|
|
507
|
+
# reaches `Cache::EngineSource.key_config_entries` whenever a source-RBS synthesizer is wired (the
|
|
508
|
+
# ADR-93 `rigor-rbs-inline` auto-wire, on by default and off throughout the spec suite), and the
|
|
509
|
+
# memo behind it is a 90 ms tree walk that cannot be eager-loaded at require time. Warming it here
|
|
510
|
+
# is what makes the worker's access a READ of a frozen String rather than a forbidden class-ivar
|
|
511
|
+
# write. `Unavailable` is left to the worker: it raises rather than assigning, so it costs the pool
|
|
512
|
+
# no more than it costs a sequential run, and swallowing it here would hide a tree the engine
|
|
513
|
+
# cannot identify.
|
|
514
|
+
begin
|
|
515
|
+
Cache::EngineSource.process_identity
|
|
516
|
+
rescue Cache::EngineSource::Unavailable
|
|
517
|
+
nil
|
|
518
|
+
end
|
|
519
|
+
|
|
477
520
|
# ADR-15 Phase 4b.x — pre-warm the RBS cache so workers serve every reflection query from the
|
|
478
521
|
# Marshal blob on disk. Without this, the first cache MISS inside a worker falls through to
|
|
479
522
|
# `RBS::EnvironmentLoader.new`, which reads a chain of non-`Ractor.shareable?` RubyGems / RBS
|
|
@@ -507,7 +550,18 @@ module Rigor
|
|
|
507
550
|
# matching explicit call for the HKT-scan outcome: every worker already demands it from its OWN
|
|
508
551
|
# environment in `#drain_reporters`, and the scan has one outcome whoever demands it, so a
|
|
509
552
|
# worker's report already says what the coordinator's own demand would.
|
|
510
|
-
|
|
553
|
+
# #1064 — the lockfile is resolved HERE, once, and travels to every worker as frozen data the way
|
|
554
|
+
# `source_files` does: Bundler's parser reads RubyGems module state a non-main Ractor may not touch,
|
|
555
|
+
# so a worker resolving its own raised `Ractor::IsolationError`, printed a "malformed Gemfile.lock"
|
|
556
|
+
# warning per worker, and analysed with the project's gem signatures silently missing.
|
|
557
|
+
locked_gems = Ractor.make_shareable(
|
|
558
|
+
Environment::LockfileResolver.locked_gems(
|
|
559
|
+
lockfile_path: @configuration.bundler_lockfile,
|
|
560
|
+
project_root: Dir.pwd,
|
|
561
|
+
auto_detect: @configuration.bundler_auto_detect
|
|
562
|
+
)
|
|
563
|
+
)
|
|
564
|
+
warm_env = prewarm_rbs_cache_for_pool(source_files: source_files, locked_gems: locked_gems)
|
|
511
565
|
snapshot_project_signature_state(warm_env)
|
|
512
566
|
snapshot_effect_annotation_carrier(warm_env&.rbs_loader)
|
|
513
567
|
record_definition_build_failures(warm_env&.rbs_loader&.definition_build_failures)
|
|
@@ -523,14 +577,15 @@ module Rigor
|
|
|
523
577
|
shareable_source_files = source_files.map { |path| path.to_s.dup.freeze }.freeze
|
|
524
578
|
|
|
525
579
|
pool = Array.new(@workers) do
|
|
526
|
-
Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files) do |configuration, cache_root, blueprints, explain, shareable_source_files| # rubocop:disable Layout/LineLength
|
|
580
|
+
Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems) do |configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems| # rubocop:disable Layout/LineLength
|
|
527
581
|
cache_store = cache_root ? Rigor::Cache::Store.new(root: cache_root) : nil
|
|
528
582
|
session = Rigor::Analysis::WorkerSession.new(
|
|
529
583
|
configuration: configuration,
|
|
530
584
|
cache_store: cache_store,
|
|
531
585
|
plugin_blueprints: blueprints,
|
|
532
586
|
explain: explain,
|
|
533
|
-
source_files: shareable_source_files
|
|
587
|
+
source_files: shareable_source_files,
|
|
588
|
+
locked_gems: locked_gems
|
|
534
589
|
)
|
|
535
590
|
main = Ractor.main
|
|
536
591
|
main.send([:prepare, session.prepare_diagnostics])
|
|
@@ -542,7 +597,10 @@ module Rigor
|
|
|
542
597
|
main.send([:file, msg, session.analyze(msg)])
|
|
543
598
|
end
|
|
544
599
|
|
|
545
|
-
|
|
600
|
+
# Issue #1051 — sent with `:done`, not with `:prepare`: this backend builds no parent session,
|
|
601
|
+
# so the worker's own table is the only place a disclosure exists, and it is complete only
|
|
602
|
+
# once the worker has finished its files.
|
|
603
|
+
main.send([:done, session.drain_reporters, session.drain_run_disclosures])
|
|
546
604
|
end
|
|
547
605
|
end
|
|
548
606
|
|
|
@@ -576,7 +634,8 @@ module Rigor
|
|
|
576
634
|
when :file
|
|
577
635
|
results_by_path[message[1]] = message[2]
|
|
578
636
|
when :done
|
|
579
|
-
merge_worker_reporters(message
|
|
637
|
+
merge_worker_reporters(message[1])
|
|
638
|
+
merge_run_disclosures(message[2])
|
|
580
639
|
end
|
|
581
640
|
end
|
|
582
641
|
|
|
@@ -604,6 +663,18 @@ module Rigor
|
|
|
604
663
|
# definition-build failures the re-analysis demanded (#696) and the HKT-scan outcome, demanded
|
|
605
664
|
# once more by the run itself (#784 — a rescued scan failure otherwise vanished with the worker).
|
|
606
665
|
# The fork backend needs none of this: it re-analyses on the parent {WorkerSession} and drains it.
|
|
666
|
+
#
|
|
667
|
+
# Issue #1051, known exception: run-scoped disclosures a plugin registered from `#prepare` are NOT
|
|
668
|
+
# recovered here — nor, since #1060, a positioned `#emit_once` batch registered there, which rides
|
|
669
|
+
# the same table. There is no session to drain — this backend builds none on the coordinator — and
|
|
670
|
+
# `#prepare` deliberately does not run on the coordinator-side registry under pool mode, so a
|
|
671
|
+
# prepare-time disclosure lives only inside the Ractor that died with it. A disclosure registered
|
|
672
|
+
# from `#diagnostics_for_file` IS recovered, because the re-analysis below runs the coordinator-side
|
|
673
|
+
# plugin instances that {DiagnosticAggregator#plugin_run_disclosure_diagnostics} reads directly.
|
|
674
|
+
# Left as is: recovering the rest means running `#prepare` on the coordinator registry, which would
|
|
675
|
+
# re-publish every cross-plugin fact for a degrade, and this backend is off by default. Since #1055
|
|
676
|
+
# the path is also rare rather than universal — it used to be taken on every run, because every
|
|
677
|
+
# worker died in its constructor on a class-ivar memo.
|
|
607
678
|
def reanalyze_degraded_in_process(degraded, results_by_path, source_files:)
|
|
608
679
|
return if degraded.empty?
|
|
609
680
|
|
|
@@ -641,7 +712,8 @@ module Rigor
|
|
|
641
712
|
project_patched_methods: project_patched_methods,
|
|
642
713
|
project_scope_seed: project_scope_seed,
|
|
643
714
|
source_files: source_files,
|
|
644
|
-
record_dependencies: @record_dependencies
|
|
715
|
+
record_dependencies: @record_dependencies,
|
|
716
|
+
template_units: template_units
|
|
645
717
|
)
|
|
646
718
|
# Force the full RBS load on the parent so children copy-on-write inherit a warm Environment
|
|
647
719
|
# rather than each rebuilding it after the fork.
|
|
@@ -662,6 +734,14 @@ module Rigor
|
|
|
662
734
|
# same project).
|
|
663
735
|
snapshot_project_signature_state(session.environment)
|
|
664
736
|
snapshot_effect_annotation_carrier(session.environment.rbs_loader)
|
|
737
|
+
# Issue #1051 — taken BEFORE the fork, for the same reason and with the same effect as the two
|
|
738
|
+
# snapshots above: `#prepare` ran on the parent when the session was built, so every disclosure a
|
|
739
|
+
# plugin registers there (the normal case — a disclosure is a fact about the project's inputs,
|
|
740
|
+
# known before a file is read) is already in this table, in the same order the sequential path
|
|
741
|
+
# would produce. Each child inherits the identical table and re-offers it in its payload, where
|
|
742
|
+
# `merge_run_disclosures` drops it as a duplicate; only a disclosure a worker registers while
|
|
743
|
+
# analysing its own slice adds anything.
|
|
744
|
+
merge_run_disclosures(session.drain_run_disclosures)
|
|
665
745
|
snapshot_fork_pool_stats(session) if @collect_stats
|
|
666
746
|
|
|
667
747
|
worker_count = [@workers, files.size].min
|
|
@@ -680,6 +760,8 @@ module Rigor
|
|
|
680
760
|
unless degraded.empty?
|
|
681
761
|
degraded.each { |path| results_by_path[path] = session.analyze(path) }
|
|
682
762
|
merge_worker_reporters(session.drain_reporters)
|
|
763
|
+
# The degraded slice ran on the parent session, so a disclosure it registered lives there.
|
|
764
|
+
merge_run_disclosures(session.drain_run_disclosures)
|
|
683
765
|
# The degraded slice was re-analysed on the parent session, so its dependency records live there,
|
|
684
766
|
# not in a child payload — fold them in alongside the successful children's.
|
|
685
767
|
@collected_dependencies.merge!(session.drain_dependencies) if @record_dependencies
|
|
@@ -704,7 +786,8 @@ module Rigor
|
|
|
704
786
|
Runtime::Jit.rearm_after_fork
|
|
705
787
|
results = slice.to_h { |path| [path, session.analyze(path)] }
|
|
706
788
|
payload = { results: results, reporters: session.drain_reporters,
|
|
707
|
-
dependencies: session.drain_dependencies
|
|
789
|
+
dependencies: session.drain_dependencies,
|
|
790
|
+
disclosures: session.drain_run_disclosures }
|
|
708
791
|
# ADR-103 WD13 — the effects slot exists only on a collecting run, so a normal `--workers N` run
|
|
709
792
|
# marshals exactly the payload it marshalled before the feature existed.
|
|
710
793
|
payload[:effects] = session.drain_effects if @record_effects
|
|
@@ -736,6 +819,7 @@ module Rigor
|
|
|
736
819
|
if payload
|
|
737
820
|
results_by_path.merge!(payload.fetch(:results))
|
|
738
821
|
merge_worker_reporters(payload.fetch(:reporters))
|
|
822
|
+
merge_run_disclosures(payload.fetch(:disclosures, []))
|
|
739
823
|
@collected_dependencies.merge!(payload.fetch(:dependencies, {})) if @record_dependencies
|
|
740
824
|
@collected_effects.merge!(payload.fetch(:effects, {})) if @record_effects
|
|
741
825
|
else
|
|
@@ -798,13 +882,14 @@ module Rigor
|
|
|
798
882
|
# index, the synthetic-method / project-patched indexes) is withheld for the same reason: nothing
|
|
799
883
|
# here dispatches.
|
|
800
884
|
# @param source_files — the WHOLE project's file list, never the analyzed subset.
|
|
801
|
-
def prewarm_rbs_cache_for_pool(source_files:)
|
|
885
|
+
def prewarm_rbs_cache_for_pool(source_files:, locked_gems: nil)
|
|
802
886
|
warm_env = Environment.for_project(
|
|
803
887
|
libraries: @configuration.libraries,
|
|
804
888
|
signature_paths: @configuration.signature_paths,
|
|
805
889
|
cache_store: @cache_store,
|
|
806
890
|
plugin_registry: plugin_registry,
|
|
807
891
|
source_files: source_files,
|
|
892
|
+
locked_gems: locked_gems,
|
|
808
893
|
**ProjectEnvironment.dependency_discovery_options(@configuration)
|
|
809
894
|
)
|
|
810
895
|
warm_env.rbs_loader&.prewarm
|
|
@@ -820,6 +905,11 @@ module Rigor
|
|
|
820
905
|
# i.e. with no synthesized RBS at all, even on a full run.
|
|
821
906
|
def analyze_files_sequentially_fallback(files, reason:, source_files: files)
|
|
822
907
|
environment = build_runner_environment(source_files: source_files)
|
|
908
|
+
# Same set, same ORDER as `#analyze_files_sequentially`. The signature state used to be hand-copied
|
|
909
|
+
# here slot by slot, which dropped `synthesized-namespace` and every conformance row. The conformance
|
|
910
|
+
# scan inside it demands the definition of every `rigor:v1:conforms-to` class, so it has to run
|
|
911
|
+
# BEFORE the definition-build failures are read below, or a failure only that scan reaches is lost.
|
|
912
|
+
snapshot_project_signature_state(environment)
|
|
823
913
|
snapshot_effect_annotation_carrier(environment.rbs_loader)
|
|
824
914
|
diagnostics = files.flat_map { |path| @analyze_file.call(path, environment) }
|
|
825
915
|
loader = environment.rbs_loader
|
|
@@ -834,10 +924,6 @@ module Rigor
|
|
|
834
924
|
record_hkt_scan_failure(hkt_scan_outcome(environment))
|
|
835
925
|
@snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
|
|
836
926
|
@snapshots.signature_paths = loader&.signature_paths || [].freeze
|
|
837
|
-
@snapshots.signature_standdowns = plugin_signature_paths? ? signature_standdowns_for(loader) : []
|
|
838
|
-
@snapshots.quarantined_signatures =
|
|
839
|
-
project_signature_paths? ? (loader&.quarantined_signatures || []) : []
|
|
840
|
-
@snapshots.env_build_failure = project_signature_paths? ? loader&.env_build_failure : nil
|
|
841
927
|
diagnostics.unshift(
|
|
842
928
|
Diagnostic.new(
|
|
843
929
|
path: ".rigor.yml", line: 1, column: 1,
|
|
@@ -864,10 +950,12 @@ module Rigor
|
|
|
864
950
|
# `:failed`, so under `--workers N` a WD12 "parsed but not honoured" row came back as
|
|
865
951
|
# `source-rbs-synthesis-failed` — a different id, and a message telling the user the file
|
|
866
952
|
# contributed nothing when all but one of its annotations bound. Every worker reads the same
|
|
867
|
-
# sources and so hands over the same entries; the reporter's own dedup collapses them.
|
|
953
|
+
# sources and so hands over the same entries; the reporter's own dedup collapses them. `line:` rides
|
|
954
|
+
# along for the same reason (#1075): a consistency error is positioned at its `.rbs` member.
|
|
868
955
|
Array(drained[:source_rbs_synthesis]).each do |entry|
|
|
869
956
|
@source_rbs_synthesis_reporter.record(
|
|
870
|
-
plugin_id: entry.plugin_id, path: entry.path, message: entry.message, kind: entry.kind
|
|
957
|
+
plugin_id: entry.plugin_id, path: entry.path, message: entry.message, kind: entry.kind,
|
|
958
|
+
line: entry.line
|
|
871
959
|
)
|
|
872
960
|
end
|
|
873
961
|
# Issue #696. Fetched with a default so an older drain stays compatible, exactly as the line above.
|
|
@@ -1022,6 +1110,10 @@ module Rigor
|
|
|
1022
1110
|
@project_patched_methods_reader.call
|
|
1023
1111
|
end
|
|
1024
1112
|
|
|
1113
|
+
def template_units
|
|
1114
|
+
@template_units_reader.call
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1025
1117
|
def project_scope_seed
|
|
1026
1118
|
@project_scope_seed_reader.call
|
|
1027
1119
|
end
|
|
@@ -8,6 +8,8 @@ require_relative "../../inference/synthetic_method_scanner"
|
|
|
8
8
|
require_relative "../../inference/project_patched_scanner"
|
|
9
9
|
require_relative "../dependency_source_inference"
|
|
10
10
|
require_relative "../project_scan"
|
|
11
|
+
require_relative "../template_units"
|
|
12
|
+
require_relative "../template_unit_collector"
|
|
11
13
|
|
|
12
14
|
module Rigor
|
|
13
15
|
module Analysis
|
|
@@ -53,10 +55,10 @@ module Rigor
|
|
|
53
55
|
Discovery = Data.define(
|
|
54
56
|
:discovered_classes, :discovered_def_nodes, :discovered_def_nestings,
|
|
55
57
|
:discovered_singleton_def_nodes, :discovered_def_sources, :discovered_singleton_def_sources,
|
|
56
|
-
:discovered_superclasses, :discovered_header_nestings, :discovered_includes,
|
|
58
|
+
:discovered_superclasses, :discovered_header_nestings, :discovered_includes, :discovered_prepends,
|
|
57
59
|
:discovered_extends, :discovered_class_sources, :constant_values, :constant_sources, :constant_writes,
|
|
58
|
-
:discovered_method_visibilities, :discovered_methods, :data_member_layouts,
|
|
59
|
-
:struct_member_layouts
|
|
60
|
+
:discovered_method_visibilities, :discovered_methods, :discovered_parameter_envelopes, :data_member_layouts,
|
|
61
|
+
:struct_member_layouts, :discovered_deferred_ranges, :discovered_refinements, :discovered_global_write_census
|
|
60
62
|
)
|
|
61
63
|
|
|
62
64
|
# Internal: drives every EAGER project-wide pre-pass — the ones whose products feed the RBS
|
|
@@ -164,24 +166,38 @@ module Rigor
|
|
|
164
166
|
# the cref Ruby resolves it in rather than by peeling the subclass's own qualified name.
|
|
165
167
|
discovered_header_nestings: def_index.fetch(:header_nestings),
|
|
166
168
|
discovered_includes: def_index.fetch(:includes),
|
|
169
|
+
# Issue #1123 — the instance-side prepend table, kept beside the include one so
|
|
170
|
+
# `Scope#user_def_through_ancestors` can search a prepended module ahead of the class's own
|
|
171
|
+
# `def`s from a sibling file the way it does within the declaring one.
|
|
172
|
+
discovered_prepends: def_index.fetch(:prepends),
|
|
167
173
|
# Issue #898 — the singleton-side mixin table, kept beside the instance-side one so a class
|
|
168
174
|
# object's `extend`s reach `Narrowing` from a sibling file the way its `include`s already do.
|
|
169
175
|
discovered_extends: def_index.fetch(:extends),
|
|
170
176
|
discovered_class_sources: def_index.fetch(:class_sources),
|
|
171
177
|
# Issue #644 — the cross-file value-constant publication table and its write attribution.
|
|
172
|
-
constant_values: def_index.fetch(:constant_values),
|
|
173
|
-
constant_sources: def_index.fetch(:constant_sources),
|
|
178
|
+
constant_values: def_index.fetch(:constant_values), constant_sources: def_index.fetch(:constant_sources),
|
|
174
179
|
constant_writes: def_index.fetch(:constant_writes),
|
|
175
180
|
discovered_method_visibilities: def_index.fetch(:method_visibilities),
|
|
176
181
|
discovered_methods: def_index.fetch(:methods),
|
|
182
|
+
discovered_parameter_envelopes: def_index.fetch(:parameter_envelopes),
|
|
177
183
|
data_member_layouts: def_index.fetch(:data_member_layouts),
|
|
178
|
-
struct_member_layouts: def_index.fetch(:struct_member_layouts)
|
|
184
|
+
struct_member_layouts: def_index.fetch(:struct_member_layouts),
|
|
185
|
+
# Issue #1097 — per-file def / block / lambda body ranges, the execution-timing table the
|
|
186
|
+
# `*_def_shadows_call?` predicates order `sig`-shadowing defs against.
|
|
187
|
+
discovered_deferred_ranges: def_index.fetch(:deferred_ranges),
|
|
188
|
+
discovered_refinements: def_index.fetch(:refinements), # Issue #1120
|
|
189
|
+
discovered_global_write_census: def_index.fetch(:global_write_census) # Issue #1367
|
|
179
190
|
)
|
|
180
191
|
end
|
|
181
192
|
|
|
182
193
|
# Builds the LSP-facing {ProjectScan} snapshot from a fresh pre-pass run. The runner adopts `result`
|
|
183
194
|
# onto its ivars first so the same registry object that ran `#prepare` (and so the populated
|
|
184
195
|
# `services.fact_store`) is the one the snapshot carries.
|
|
196
|
+
#
|
|
197
|
+
# Since #1038 the snapshot also carries the template-unit index, so a long-lived owner's per-buffer
|
|
198
|
+
# publishes carry it instead of re-running every plugin transform per keystroke. It is built through
|
|
199
|
+
# {TemplateUnitCollector.collect_for_scan}, which owns both of that slot's rules — no buffer binding,
|
|
200
|
+
# and an ADR-87 run scope around the packs a later run revalidates against.
|
|
185
201
|
def build_project_scan(result)
|
|
186
202
|
ProjectScan.new(
|
|
187
203
|
plugin_registry: result.plugin_registry,
|
|
@@ -189,7 +205,10 @@ module Rigor
|
|
|
189
205
|
synthetic_method_index: result.synthetic_method_index,
|
|
190
206
|
project_patched_methods: result.project_patched_methods,
|
|
191
207
|
plugin_prepare_diagnostics: result.cached_plugin_prepare_diagnostics.dup.freeze,
|
|
192
|
-
pre_eval_diagnostics: result.pre_eval_diagnostics_from_scanner.dup.freeze
|
|
208
|
+
pre_eval_diagnostics: result.pre_eval_diagnostics_from_scanner.dup.freeze,
|
|
209
|
+
template_units: TemplateUnitCollector.collect_for_scan(
|
|
210
|
+
registry: result.plugin_registry, strict: @configuration.cache_validation_strict?
|
|
211
|
+
)
|
|
193
212
|
)
|
|
194
213
|
end
|
|
195
214
|
|