rigortype 0.3.9 → 0.4.1
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 +5 -4
- 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/08-understanding-errors.md +3 -2
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +65 -4
- data/docs/manual/03-configuration.md +12 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- 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 +427 -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 +638 -136
- data/lib/rigor/analysis/dependency_recorder.rb +222 -17
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
- data/lib/rigor/analysis/incremental_session.rb +309 -53
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- 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 +3 -1
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +46 -18
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
- data/lib/rigor/analysis/runner.rb +603 -54
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +93 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +409 -0
- data/lib/rigor/analysis/worker_session.rb +57 -13
- 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 +45 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +102 -8
- 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 +63 -4
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/check_command.rb +83 -13
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- 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/unused_command.rb +3 -3
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +24 -3
- data/lib/rigor/configuration.rb +71 -7
- 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 +49 -37
- 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/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/lockfile_resolver.rb +27 -1
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_loader.rb +400 -134
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +104 -96
- 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 +88 -26
- 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 +393 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +3 -2
- 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/definer_resolution.rb +266 -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 +1665 -538
- data/lib/rigor/inference/external_ancestor_resolution.rb +264 -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 +488 -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 +179 -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 +901 -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/module_function_state.rb +213 -0
- data/lib/rigor/inference/multi_target_binder.rb +345 -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 +674 -124
- 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 +13 -11
- 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/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +5913 -701
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +3249 -497
- 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 +196 -13
- 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/io_boundary.rb +82 -4
- data/lib/rigor/plugin/isolation.rb +1 -1
- 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/trust_policy.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +20 -9
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +4 -5
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +146 -0
- data/lib/rigor/reflection/constant_path.rb +41 -13
- data/lib/rigor/reflection.rb +73 -107
- data/lib/rigor/scope/discovery_index.rb +351 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +884 -0
- data/lib/rigor/scope.rb +981 -198
- 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 +580 -47
- 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 +112 -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-ffi/lib/rigor/plugin/ffi.rb +10 -0
- 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 +14 -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 +2 -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 +177 -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 +46 -18
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +40 -10
- 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 +132 -1
|
@@ -48,6 +48,7 @@ require_relative "runner/run_snapshots"
|
|
|
48
48
|
require_relative "runner/project_pre_passes"
|
|
49
49
|
require_relative "runner/pool_coordinator"
|
|
50
50
|
require_relative "runner/diagnostic_aggregator"
|
|
51
|
+
require_relative "template_units"
|
|
51
52
|
require_relative "runner/effect_envelope_pass"
|
|
52
53
|
require_relative "runner/effect_annotation_residual_pass"
|
|
53
54
|
require_relative "runner/buffer_pool_dispatcher"
|
|
@@ -107,6 +108,32 @@ module Rigor
|
|
|
107
108
|
@effect_table || Effects::EffectTable.empty
|
|
108
109
|
end
|
|
109
110
|
|
|
111
|
+
# ADR-103 WD9 (#391) — the envelope index this run resolved, for `rigor sig-gen`'s annotation
|
|
112
|
+
# emission to consult. Public for the same reason {#effect_table} is: a consumer outside the
|
|
113
|
+
# diagnostic stream needs what the run already computed, and rebuilding it from configuration
|
|
114
|
+
# alone would read a narrower set of strata — the accepted signatures and the rbs-inline virtual
|
|
115
|
+
# RBS both come from the built environment.
|
|
116
|
+
#
|
|
117
|
+
# Built on demand when the run never analysed a file. A WARM whole-run cache hit serves the
|
|
118
|
+
# propagated table from cache, so {#analyze_with_effects} — the only caller of
|
|
119
|
+
# {#effect_envelope_index} — never runs and the ivar stays nil. Returning an empty index there
|
|
120
|
+
# would silently drop sig-gen's "the author already declared a bound" gate on exactly the runs a
|
|
121
|
+
# user makes most often: the same `sig-gen` invocation would withhold on its first run and emit on
|
|
122
|
+
# its second. `@run_environment` is resolved before the cache is consulted, so the warm build reads
|
|
123
|
+
# the same strata the cold one does.
|
|
124
|
+
#
|
|
125
|
+
# `@run_environment` is nil only when the run resolved no environment at all — a run over zero
|
|
126
|
+
# project files, or one that never reached {#compute_run_diagnostics}. The build then falls back to
|
|
127
|
+
# the configuration-only strata (`sig/` annotations, class-level ones and `effects.envelopes:`,
|
|
128
|
+
# without the accepted signatures or the rbs-inline virtual RBS), which is the narrower answer but
|
|
129
|
+
# not a wrong one: a run with no environment analysed no file, so there is no candidate for the
|
|
130
|
+
# missing strata to have bounded.
|
|
131
|
+
def effect_envelopes
|
|
132
|
+
return @effect_envelope_index if @effect_envelope_index
|
|
133
|
+
|
|
134
|
+
effect_envelope_index(@run_environment)
|
|
135
|
+
end
|
|
136
|
+
|
|
110
137
|
# ADR-103 WD2 / WD6 / WD10 / #387 — the loaded plugins' effect contributions, compiled once per
|
|
111
138
|
# process. Memoised on first use rather than built in the constructor for two reasons: the plugin
|
|
112
139
|
# registry is adopted after construction (`apply_prebuilt` / the plugin-load pre-pass), and the
|
|
@@ -270,11 +297,15 @@ module Rigor
|
|
|
270
297
|
record_dependencies: false, record_self_calls: false, analyze_only: nil,
|
|
271
298
|
seed_bundles: nil, collect_seed_bundles: false, param_inferred_types: nil,
|
|
272
299
|
discovery_seed: nil, no_tolerated_effects: false,
|
|
273
|
-
restored_run_level_rows: nil)
|
|
300
|
+
restored_run_level_rows: nil, served_per_file_diagnostics: nil)
|
|
274
301
|
@configuration = configuration
|
|
275
302
|
# Issues #796 / #794 — the previous full run's run-level rows, supplied only by
|
|
276
303
|
# {IncrementalSession} on a narrowed run; see {PoolCoordinator#replay_restored_run_level_rows}.
|
|
277
304
|
@restored_run_level_rows = restored_run_level_rows
|
|
305
|
+
# Issue #1524 — `{path => rows}` the {IncrementalSession} serves from its per-file cache for the files a
|
|
306
|
+
# narrowed run does not analyse, spliced into this run's per-file stream by
|
|
307
|
+
# {#interleave_served_diagnostics}. Empty on every other run.
|
|
308
|
+
@served_per_file_diagnostics = served_per_file_diagnostics || {}
|
|
278
309
|
@explain = explain
|
|
279
310
|
@cache_store = enforce_read_only_cache(cache_store, buffer)
|
|
280
311
|
@plugin_requirer = plugin_requirer
|
|
@@ -314,6 +345,10 @@ module Rigor
|
|
|
314
345
|
# check reads.
|
|
315
346
|
@no_tolerated_effects = no_tolerated_effects
|
|
316
347
|
@file_effects = {}
|
|
348
|
+
# #392 — the run's template units, synthesised once per run after the plugins load (see
|
|
349
|
+
# {#template_units}). nil until then; a project whose plugins declare no `template_globs:` resolves
|
|
350
|
+
# to the shared empty index and pays one `Array#empty?` for the whole run.
|
|
351
|
+
@template_units = nil
|
|
317
352
|
@effect_table = nil
|
|
318
353
|
@effects_served_from_cache = false
|
|
319
354
|
# #482 — set only by a run served from the summary entry: the two derived tables it carries, and
|
|
@@ -322,6 +357,12 @@ module Rigor
|
|
|
322
357
|
@cached_effect_ancestry = nil
|
|
323
358
|
@cached_collections_loader = nil
|
|
324
359
|
@run_dependency_descriptor = nil
|
|
360
|
+
# ADR-45 WD2 (#1507) — the plugin reads each file's analysis made (`{path => [row]}`), and the boundary
|
|
361
|
+
# rows as they stood around the per-file loop; see {#incremental_slot_rows}. Recorded only by a
|
|
362
|
+
# dependency-recording (incremental) run.
|
|
363
|
+
@analysis_reads = {}
|
|
364
|
+
@pre_analysis_plugin_rows = nil
|
|
365
|
+
@post_analysis_plugin_rows = nil
|
|
325
366
|
# Memoised activation decision for the `call.self-undefined-method` rule (nil = not yet computed).
|
|
326
367
|
# See `self_undefined_rule_active?`.
|
|
327
368
|
@self_undefined_rule_active = nil
|
|
@@ -378,8 +419,17 @@ module Rigor
|
|
|
378
419
|
# Issue #682 — the `Module.nesting` each class / module declaration HEADER is written in.
|
|
379
420
|
@project_discovered_header_nestings = {}.freeze
|
|
380
421
|
@project_discovered_includes = {}.freeze
|
|
422
|
+
# Issue #1123 — the instance-side prepend table (`prepend M`, and `Recv.prepend(M)`), in
|
|
423
|
+
# instance-ancestor SEARCH order.
|
|
424
|
+
@project_discovered_prepends = {}.freeze
|
|
381
425
|
# Issue #898 — the singleton-side twin of the include table (`extend M` / `extend self`).
|
|
382
426
|
@project_discovered_extends = {}.freeze
|
|
427
|
+
@project_unpositioned_mixins = {}.freeze
|
|
428
|
+
@project_discovered_deferred_ranges = {}.freeze
|
|
429
|
+
# Issue #1120 — `{refined class => {method => [refining modules]}}`, every `refine X do … end` body.
|
|
430
|
+
@project_discovered_refinements = {}.freeze
|
|
431
|
+
# Issue #1367 — the project's `Inference::GlobalWriteCensus`, which the `global.*` write rules read.
|
|
432
|
+
@project_discovered_global_write_census = Set.new.freeze
|
|
383
433
|
@project_discovered_class_sources = {}.freeze
|
|
384
434
|
# Issue #644 — the cross-file VALUE-constant publication table (`{qualified name => Type::Constant}`,
|
|
385
435
|
# literal writes only) and its per-name write attribution. The first seeds `in_source_constants` on
|
|
@@ -390,8 +440,15 @@ module Rigor
|
|
|
390
440
|
# ...and the raw per-(name, path) publication census the incremental producer diffs.
|
|
391
441
|
@project_constant_writes = {}.freeze
|
|
392
442
|
@published_constant_name_set = nil
|
|
443
|
+
@constant_writer_index = nil
|
|
444
|
+
@constant_shadower_index = nil
|
|
393
445
|
@project_discovered_method_visibilities = {}.freeze
|
|
394
446
|
@project_discovered_methods = {}.freeze
|
|
447
|
+
# ADR-119 WD1 — the `possible_*` / `contested_*` siblings of the paired members, ONE Hash keyed by sibling
|
|
448
|
+
# name so no adoption or seed path names a sibling and none can drop one.
|
|
449
|
+
@project_discovery_siblings = Scope::DiscoveryIndex::EMPTY_SIBLINGS
|
|
450
|
+
# Issue #992 — the cross-file parameter-envelope table `call.wrong-arity` reads for an undeclared `def`.
|
|
451
|
+
@project_discovered_parameter_envelopes = {}.freeze
|
|
395
452
|
@project_data_member_layouts = {}.freeze
|
|
396
453
|
@project_struct_member_layouts = {}.freeze
|
|
397
454
|
# Issue #684 — set per run by {#project_discovery_expansion}; nil on every path that does not widen.
|
|
@@ -453,12 +510,21 @@ module Rigor
|
|
|
453
510
|
@snapshots.reset_for_run
|
|
454
511
|
# Per-run reset of the deferred-discovery memo (see `#ensure_project_discovery`).
|
|
455
512
|
@project_discovery_done = false
|
|
513
|
+
# #392 — re-derive the template units each run: their source files can change between two runs of
|
|
514
|
+
# one long-lived Runner (the LSP's), and a unit is never served from a cache of its own. Since
|
|
515
|
+
# #1038 "re-derive" is not "recompile": a `prebuilt:` runner carries the snapshot's warm index into
|
|
516
|
+
# {#template_units}, which still re-expands every claim and revalidates every template against the
|
|
517
|
+
# filesystem, and only compiles what actually moved.
|
|
518
|
+
@template_units = nil
|
|
456
519
|
# Per-run reset of the environment the cacheable path resolves, reused by the envelope pass so a
|
|
457
520
|
# run never builds two.
|
|
458
521
|
@run_environment = nil
|
|
459
522
|
# #788 — the per-file reader is assigned only on the analysis (miss) path; reset it here so a run the
|
|
460
523
|
# ADR-45 result cache serves does not answer with the previous run's rows.
|
|
461
524
|
@per_file_diagnostics = [].freeze
|
|
525
|
+
@analysis_reads = {}
|
|
526
|
+
@pre_analysis_plugin_rows = nil
|
|
527
|
+
@post_analysis_plugin_rows = nil
|
|
462
528
|
# ADR-84 WD2 — roll the return-memo bucket: a fresh frozen token per run makes every per-file scope
|
|
463
529
|
# of THIS run share one memo bucket while entries from any earlier run in this process (stale after
|
|
464
530
|
# an edit) become unreachable.
|
|
@@ -746,7 +812,10 @@ module Rigor
|
|
|
746
812
|
@run_served_from_cache = false
|
|
747
813
|
return assemble_run_diagnostics(expansion) unless run_result_cacheable?
|
|
748
814
|
|
|
749
|
-
|
|
815
|
+
# #392 — `source_files:` is what a plugin's `source_rbs_synthesizer` is offered at env-build time,
|
|
816
|
+
# and a template unit is not Ruby the synthesiser can read (rbs-inline would be handed `.rbx`
|
|
817
|
+
# bytes). The `.rb` expansion is the right set here; the units join the ANALYSED set only.
|
|
818
|
+
environment = @pool_coordinator.resolve_sequential_environment(source_files: expansion.fetch(:files))
|
|
750
819
|
# Lazy-files descriptor: the cache KEY reads only `gems` + `configs`; the RBS signature-tree `files`
|
|
751
820
|
# are digested solely by `run_dependency_descriptor` on a MISS, so a warm HIT never walks the tree.
|
|
752
821
|
rbs_descriptor = if environment&.rbs_loader
|
|
@@ -776,6 +845,11 @@ module Rigor
|
|
|
776
845
|
# then hit — the stats it would otherwise suppress were all gathered.
|
|
777
846
|
@run_served_from_cache = !computed && analysis.nil?
|
|
778
847
|
diagnostics
|
|
848
|
+
rescue Inference::DeclarationWalk::ContractError
|
|
849
|
+
# A broken walk contract in the project pre-pass (a shadow-harness divergence, an unknown variant) is
|
|
850
|
+
# not a cache failure: the fallback below would re-run the analysis with the pre-pass marked done and
|
|
851
|
+
# no project index, and report the resulting false positives instead of the failure.
|
|
852
|
+
raise
|
|
779
853
|
rescue StandardError
|
|
780
854
|
# The result cache must never break a run. If anything in the cache path fails, fall back to a
|
|
781
855
|
# direct, uncached analysis.
|
|
@@ -998,7 +1072,14 @@ module Rigor
|
|
|
998
1072
|
def close_effect_graph
|
|
999
1073
|
return unless @record_effects
|
|
1000
1074
|
|
|
1001
|
-
|
|
1075
|
+
# `declined_unit_keys:` — #1065. The templates a plugin claimed and produced no unit for; a render
|
|
1076
|
+
# edge's format fallback must not resolve past one of them, because the file exists and the
|
|
1077
|
+
# framework runs it, so the other format's effects are not what that render produces.
|
|
1078
|
+
@effect_table = Effects::Propagator.propagate(
|
|
1079
|
+
effect_collection,
|
|
1080
|
+
discharge: effect_discharge,
|
|
1081
|
+
declined_unit_keys: template_units.declined_unit_keys
|
|
1082
|
+
)
|
|
1002
1083
|
rescue StandardError
|
|
1003
1084
|
@effect_table = Effects::EffectTable.empty
|
|
1004
1085
|
end
|
|
@@ -1029,6 +1110,11 @@ module Rigor
|
|
|
1029
1110
|
# untouched so its lazy build timing is unchanged.
|
|
1030
1111
|
environment = seed_pre_eval_constants(expansion, environment)
|
|
1031
1112
|
diagnostics = @diagnostic_aggregator.pre_file_diagnostics(expansion)
|
|
1113
|
+
# #392 — a template transform that raised, a template that could not be read, or a unit naming a
|
|
1114
|
+
# file it was not offered. Reported through the same `:plugin_loader` `runtime-error` envelope a
|
|
1115
|
+
# raise from `#diagnostics_for_file` uses (ADR-2 § "Plugin Trust and I/O Policy"), rather than
|
|
1116
|
+
# dropped in silence: a plugin author whose view stopped being analysed has nothing else to read.
|
|
1117
|
+
diagnostics += template_unit_failure_diagnostics
|
|
1032
1118
|
# ADR-46 — record which project files this run actually analyzed (the `analyze_only` subset, or
|
|
1033
1119
|
# all of them). The incremental orchestrator serves every analyzed-but-not-affected file from the
|
|
1034
1120
|
# per-file cache, so it needs the full analyzed set to subtract the affected closure from.
|
|
@@ -1045,6 +1131,11 @@ module Rigor
|
|
|
1045
1131
|
# override re-stamps it; and a pool backend folds `.rigor.yml`-positioned prepare / pool-degraded rows
|
|
1046
1132
|
# into the same return, which the slice drops. The run's own stream is stamped once, at the end.
|
|
1047
1133
|
diagnostics += analyze_targets(targets, environment: environment, project_files: expansion.fetch(:files))
|
|
1134
|
+
# Issue #1060 — the positioned batches plugins registered through `Plugin::Base#emit_once`, read once
|
|
1135
|
+
# every worker payload has been merged. Kept beside the per-file stream (their rows name files and
|
|
1136
|
+
# lines) but outside `#per_file_diagnostics`, which `analyze_targets` has already sliced, so no one
|
|
1137
|
+
# file's incremental cache entry carries a project-wide batch.
|
|
1138
|
+
diagnostics += @diagnostic_aggregator.plugin_run_emission_diagnostics
|
|
1048
1139
|
# ADR-103 WD12 — the effect fixpoint, in the post-pool aggregation slot beside the conformance
|
|
1049
1140
|
# results. Graph-only over a finite lattice, so it is a plain worklist to a true fixpoint; it
|
|
1050
1141
|
# contributes NO diagnostics and its result leaves through `#effect_table`, never through the
|
|
@@ -1053,6 +1144,18 @@ module Rigor
|
|
|
1053
1144
|
diagnostics + post_analysis_diagnostic_streams
|
|
1054
1145
|
end
|
|
1055
1146
|
|
|
1147
|
+
def template_unit_failure_diagnostics
|
|
1148
|
+
template_units.failures.map do |failure|
|
|
1149
|
+
Diagnostic.new(
|
|
1150
|
+
path: failure.path, line: 1, column: 1,
|
|
1151
|
+
message: "plugin #{failure.plugin_id.inspect} produced no template unit for " \
|
|
1152
|
+
"#{failure.path}: #{failure.message}",
|
|
1153
|
+
severity: :error, rule: "runtime-error", source_family: :plugin_loader
|
|
1154
|
+
)
|
|
1155
|
+
end
|
|
1156
|
+
end
|
|
1157
|
+
private :template_unit_failure_diagnostics
|
|
1158
|
+
|
|
1056
1159
|
# The rbs-coverage build-failure ladder plus every remaining post-analysis stream
|
|
1057
1160
|
# `#assemble_run_diagnostics` appends, in their fixed contract order (see the comments at each call
|
|
1058
1161
|
# below and at the streams' own definitions). Split out of `#assemble_run_diagnostics` so that
|
|
@@ -1077,6 +1180,11 @@ module Rigor
|
|
|
1077
1180
|
diagnostics += @diagnostic_aggregator.rbs_extended_reporter_diagnostics
|
|
1078
1181
|
diagnostics += @diagnostic_aggregator.boundary_cross_diagnostics
|
|
1079
1182
|
diagnostics += @diagnostic_aggregator.source_rbs_synthesis_diagnostics
|
|
1183
|
+
# Issue #1051 — read after `analyze_targets` for the same reason the refusal stream below is: a
|
|
1184
|
+
# plugin may register a disclosure from `#prepare` (the usual case) or while analysing a file, and
|
|
1185
|
+
# both have to be in before the table is harvested. Ordered before the refusals so the run-level
|
|
1186
|
+
# plugin rows read plugin-authored notice first, engine-authored envelope second.
|
|
1187
|
+
diagnostics += @diagnostic_aggregator.plugin_run_disclosure_diagnostics
|
|
1080
1188
|
# Issue #959 — read LAST (after `analyze_targets`, which already ran before this method is called),
|
|
1081
1189
|
# so every per-file plugin call has had its chance to record a refusal on its (per-plugin-instance
|
|
1082
1190
|
# memoised) `IoBoundary`, alongside the prepare-time refusals the same boundary instance may
|
|
@@ -1089,17 +1197,55 @@ module Rigor
|
|
|
1089
1197
|
# `#per_file_diagnostics`: the `analyze_files` return, stamped with the same severity profile the run's
|
|
1090
1198
|
# own stream gets (`SeverityStamp` drops `:off` rows and re-stamps overrides, and the per-file cache never
|
|
1091
1199
|
# re-stamps), sliced to the rows positioned at the targets. Returns the RAW return for the run's stream,
|
|
1092
|
-
# which is stamped once, at the end of `#run_analysis
|
|
1200
|
+
# which is stamped once, at the end of `#run_analysis` — on a narrowed incremental run with the served
|
|
1201
|
+
# files' rows spliced in (#1524), which `#per_file_diagnostics` never carries.
|
|
1093
1202
|
def analyze_targets(targets, environment:, project_files:)
|
|
1094
|
-
raw =
|
|
1203
|
+
raw = template_units.remap(
|
|
1204
|
+
@pool_coordinator.analyze_files(targets, environment: environment, project_files: project_files)
|
|
1205
|
+
)
|
|
1206
|
+
# ADR-45 WD2 (#1507) — the boundaries as the per-file loop left them; with the snapshot the first
|
|
1207
|
+
# analysed file took, the rows {#incremental_slot_rows} checks every analysis-time read against.
|
|
1208
|
+
@post_analysis_plugin_rows = plugin_boundary_rows if @record_dependencies
|
|
1095
1209
|
analysed = targets.to_set
|
|
1096
1210
|
@per_file_diagnostics = @diagnostic_aggregator.apply_severity_profile(raw)
|
|
1097
1211
|
.select { |diagnostic| analysed.include?(diagnostic.path) }
|
|
1098
1212
|
.freeze
|
|
1099
|
-
raw
|
|
1213
|
+
interleave_served_diagnostics(raw, analysed, project_files | targets)
|
|
1100
1214
|
end
|
|
1101
1215
|
private :analyze_targets
|
|
1102
1216
|
|
|
1217
|
+
# Issue #1524 — splices the rows {IncrementalSession} serves from its per-file cache into this narrowed
|
|
1218
|
+
# run's per-file stream, each file's rows where a full run of the same tree lists them. A full run
|
|
1219
|
+
# analyses `order` (the project's files, then any buffer and template-unit targets) one file after
|
|
1220
|
+
# another and a narrowed run a subsequence of it, so a served file's rows go just before the first row
|
|
1221
|
+
# positioned at an analysed file later in `order`, or at the end when there is none. Only such a row
|
|
1222
|
+
# marks a place, so the rows this run produced keep their relative order and a pool backend's
|
|
1223
|
+
# `.rigor.yml` rows stay at the head. Splicing here rather than into the finished result keeps the
|
|
1224
|
+
# run-level streams on either side of the per-file stream, where a full run has them. The served rows
|
|
1225
|
+
# are already severity-resolved and the run's final stamp leaves them unchanged: a resolved severity
|
|
1226
|
+
# resolves to itself, and a row the profile turns `:off` is never cached.
|
|
1227
|
+
def interleave_served_diagnostics(raw, analysed, order)
|
|
1228
|
+
served = @served_per_file_diagnostics
|
|
1229
|
+
return raw if served.empty?
|
|
1230
|
+
|
|
1231
|
+
rank = order.each_with_index.to_h
|
|
1232
|
+
# A served path the expansion no longer lists (a file deleted between the session's listing and this
|
|
1233
|
+
# run) keeps its rows, after every other file's, as the concatenation this replaced did.
|
|
1234
|
+
known, unknown = served.keys.partition { |path| rank.key?(path) }
|
|
1235
|
+
pending = known.sort_by { |path| rank.fetch(path) }
|
|
1236
|
+
merged = []
|
|
1237
|
+
raw.each do |diagnostic|
|
|
1238
|
+
# `order` lists every target, so an analysed path always has a rank; one that did not would only
|
|
1239
|
+
# mark no place, since a misplaced row must never abort the run (ADR-5).
|
|
1240
|
+
position = analysed.include?(diagnostic.path) ? rank.fetch(diagnostic.path, -1) : -1
|
|
1241
|
+
merged.concat(served.fetch(pending.shift)) while pending.any? && rank.fetch(pending.first) < position
|
|
1242
|
+
merged << diagnostic
|
|
1243
|
+
end
|
|
1244
|
+
(pending + unknown).each { |path| merged.concat(served.fetch(path)) }
|
|
1245
|
+
merged
|
|
1246
|
+
end
|
|
1247
|
+
private :interleave_served_diagnostics
|
|
1248
|
+
|
|
1103
1249
|
# ADR-67 WD6a — the check-walk parameter-inference pre-pass. Populates `@project_param_inferred_types`
|
|
1104
1250
|
# (read by `project_scope_seed_tables`) with the call-site union of every undeclared parameter, running
|
|
1105
1251
|
# ONE round (a single hop of call-site → param typing; the protection scan's three-round fixpoint stays a
|
|
@@ -1199,7 +1345,8 @@ module Rigor
|
|
|
1199
1345
|
# reconstructs `rbs.libraries` from config) can never drift out of key agreement.
|
|
1200
1346
|
RunCacheKey.descriptor(
|
|
1201
1347
|
configuration: @configuration, files: expansion.fetch(:files),
|
|
1202
|
-
explain: @explain, rbs_config_entries: rbs_descriptor.configs
|
|
1348
|
+
explain: @explain, rbs_config_entries: rbs_descriptor.configs,
|
|
1349
|
+
template_units_digest: template_units.digest
|
|
1203
1350
|
)
|
|
1204
1351
|
end
|
|
1205
1352
|
|
|
@@ -1216,30 +1363,210 @@ module Rigor
|
|
|
1216
1363
|
|
|
1217
1364
|
def build_run_dependency_descriptor(expansion, rbs_descriptor)
|
|
1218
1365
|
entries = analyzed_file_entries(expansion) + discovery_file_entries(expansion) +
|
|
1219
|
-
pre_eval_file_entries + rbs_descriptor.files
|
|
1366
|
+
pre_eval_file_entries + template_unit_file_entries + rbs_descriptor.files
|
|
1220
1367
|
# #979 — the signature ROOTS' listings, one glob row each. `rbs_descriptor.files` covers only the
|
|
1221
1368
|
# `.rbs` files that existed while the run read them, so without these a signature file written after
|
|
1222
1369
|
# the run left the slot validating fresh and the warm run answered without it.
|
|
1223
|
-
globs = rbs_descriptor.globs
|
|
1370
|
+
globs = rbs_descriptor.globs + template_unit_glob_entries
|
|
1371
|
+
boundary = plugin_boundary_rows
|
|
1372
|
+
entries.concat(boundary.files)
|
|
1373
|
+
globs.concat(boundary.globs).concat(producer_watch_glob_entries)
|
|
1374
|
+
Cache::Descriptor.new(files: entries, globs: globs.uniq)
|
|
1375
|
+
end
|
|
1376
|
+
|
|
1377
|
+
# Every file a plugin read or probed through its `IoBoundary` (#577 / #613), and every directory it listed
|
|
1378
|
+
# (#954), as the boundaries stand when asked.
|
|
1379
|
+
def plugin_boundary_rows
|
|
1380
|
+
files = []
|
|
1381
|
+
globs = []
|
|
1224
1382
|
@plugin_registry.plugins.each do |plugin|
|
|
1225
1383
|
# Read the boundary WITHOUT triggering its lazy `@io_boundary ||=` initializer: plugin instances
|
|
1226
1384
|
# are frozen after the run, and a plugin that never built a boundary read no files through it,
|
|
1227
1385
|
# so it contributes no dependencies.
|
|
1228
1386
|
boundary = plugin.instance_variable_get(:@io_boundary)
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1387
|
+
next unless boundary
|
|
1388
|
+
|
|
1389
|
+
# #629 — a boundary carries GlobEntry listing rows too (`IoBoundary#list_directory`), and they
|
|
1390
|
+
# are the only edge back to a file the run decided was ABSENT from a directory it consulted.
|
|
1391
|
+
boundary_descriptor = boundary.cache_descriptor
|
|
1392
|
+
files.concat(boundary_descriptor.files)
|
|
1393
|
+
globs.concat(boundary_descriptor.globs)
|
|
1394
|
+
end
|
|
1395
|
+
Cache::Descriptor.new(files: files, globs: globs)
|
|
1396
|
+
end
|
|
1397
|
+
|
|
1398
|
+
# One glob row per producer `watch:` pattern (ADR-60 WD3), recomputed against the tree as it stands.
|
|
1399
|
+
def producer_watch_glob_entries
|
|
1400
|
+
@plugin_registry.plugins.flat_map do |plugin|
|
|
1401
|
+
plugin.class.producers.each_value.flat_map do |prod|
|
|
1402
|
+
prod[:watch] ? plugin.send(:watch_glob_entries, prod[:watch]) : []
|
|
1235
1403
|
end
|
|
1236
|
-
|
|
1237
|
-
|
|
1404
|
+
end
|
|
1405
|
+
end
|
|
1406
|
+
|
|
1407
|
+
# ADR-45 WD2 (#1507) — the rows `rigor check --incremental`'s run-result slot records beside the analysed
|
|
1408
|
+
# files (whose digests {IncrementalSession} holds itself). Every run reads these inputs again and re-derives
|
|
1409
|
+
# its answer from them; they are split by when the row was taken, which decides whether the session's
|
|
1410
|
+
# {IncrementalRunSlot::WriteGuard} must vouch for it:
|
|
1411
|
+
#
|
|
1412
|
+
# - `observed` — every plugin row (`#prepare`, producers, template synthesis, and this run's per-file reads
|
|
1413
|
+
# too), recorded by the {Plugin::IoBoundary} as the plugin read. It includes each producer's `watch:`
|
|
1414
|
+
# globs, which a producer replays into its boundary whether it computed or was served (#1558). A save
|
|
1415
|
+
# after the read leaves the row stale, so no guard is needed.
|
|
1416
|
+
# - `derived` — rows taken when the run ENDS: the template files and their claimed globs (#392, re-analysed
|
|
1417
|
+
# every run), and one existence row per analysis root and per `pre_eval:` entry. A run's path errors (`no
|
|
1418
|
+
# such file or directory`, `pre-eval.file-not-found`) answer to those last rows; the plain slot records
|
|
1419
|
+
# neither, and the incremental path regenerates the errors every run, so a slot that served one past the
|
|
1420
|
+
# edit that retracts it would print what no run of that tree prints. Existence rows carry ADR-45 WD1b's
|
|
1421
|
+
# bound: a directory replaced by a file of the same name stays fresh.
|
|
1422
|
+
# - `by_file` — `{path => Descriptor}`, the plugin reads each file's analysis made
|
|
1423
|
+
# ({Plugin::IoBoundary.attributing}). A narrowed run analyses only its closure, so the session carries the
|
|
1424
|
+
# other files' reads forward from the previous slot.
|
|
1425
|
+
#
|
|
1426
|
+
# The template rows stand in for the `template-units` slot the plain key carries, which only a run can
|
|
1427
|
+
# compute. That digest is a function of each unit's compiled Ruby (the template's bytes, and whatever else
|
|
1428
|
+
# the transform read, through its boundary), its transform (the plugin, whose identity and configuration
|
|
1429
|
+
# the key carries) and the synthesis version (the engine's): each of those has a row or a key slot.
|
|
1430
|
+
#
|
|
1431
|
+
# The inputs a recheck does NOT re-derive are {#baseline_dependency_rows}. nil when this run cannot vouch for
|
|
1432
|
+
# its rows — a pool run, whose workers' reads never reach this process; a run that recorded no dependencies,
|
|
1433
|
+
# whose analysis no session owns; or a run in which a boundary row changed during the per-file loop that no
|
|
1434
|
+
# file's analysis was credited with, such as a read from a thread the plugin started.
|
|
1435
|
+
IncrementalSlotRows = Data.define(:observed, :derived, :by_file)
|
|
1436
|
+
|
|
1437
|
+
# @param roots — the analysis roots the run was given.
|
|
1438
|
+
def incremental_slot_rows(roots:)
|
|
1439
|
+
return nil if pool_mode? || !@record_dependencies
|
|
1440
|
+
return nil unless analysis_reads_attributed?
|
|
1441
|
+
|
|
1442
|
+
IncrementalSlotRows.new(
|
|
1443
|
+
observed: plugin_boundary_rows,
|
|
1444
|
+
derived: Cache::Descriptor.new(
|
|
1445
|
+
files: template_unit_file_entries + existence_entries(Array(roots) + @configuration.pre_eval),
|
|
1446
|
+
globs: template_unit_glob_entries
|
|
1447
|
+
),
|
|
1448
|
+
by_file: @analysis_reads.transform_values do |reads|
|
|
1449
|
+
Cache::Descriptor.new(
|
|
1450
|
+
files: reads.grep(Cache::Descriptor::FileEntry).uniq,
|
|
1451
|
+
globs: reads.grep(Cache::Descriptor::GlobEntry).uniq
|
|
1452
|
+
)
|
|
1453
|
+
end
|
|
1454
|
+
)
|
|
1455
|
+
end
|
|
1456
|
+
|
|
1457
|
+
# Whether every boundary row that appeared or changed during the per-file loop is one some file's analysis
|
|
1458
|
+
# was credited with. A row outside every file's attribution would ride this run's `run` rows only, and the
|
|
1459
|
+
# next narrowed run — which does not repeat the read — would drop it.
|
|
1460
|
+
def analysis_reads_attributed?
|
|
1461
|
+
post = @post_analysis_plugin_rows || plugin_boundary_rows
|
|
1462
|
+
pre = @pre_analysis_plugin_rows || post
|
|
1463
|
+
credited = @analysis_reads.each_value.flat_map(&:itself).to_set
|
|
1464
|
+
(post.files - pre.files).all? { |row| credited.include?(row) } &&
|
|
1465
|
+
(post.globs - pre.globs).all? { |row| credited.include?(row) }
|
|
1466
|
+
end
|
|
1467
|
+
|
|
1468
|
+
# ADR-45 WD2 (#1507) — the rows a FULL run records for the inputs an incremental recheck does not re-derive,
|
|
1469
|
+
# which the session therefore carries unchanged from the full run that recorded them to every later slot:
|
|
1470
|
+
#
|
|
1471
|
+
# - the signature tree the plain slot records (`RbsDescriptor.file_entries` / `.glob_entries` over the loader
|
|
1472
|
+
# roots this run's analysis resolved), and an existence row per signature root, configured or the
|
|
1473
|
+
# auto-detected `sig` — a recheck rebuilds no environment unless its closure is non-empty, and the snapshot
|
|
1474
|
+
# fingerprint digests the same roots (configured, or the auto-detected `sig`), so a change to one drops the
|
|
1475
|
+
# snapshot and forces a full run rather than reaching a recheck;
|
|
1476
|
+
# - the discovered-not-analysed files (#684) and a listing row per discovery root — a narrowed recheck does
|
|
1477
|
+
# not widen discovery at all;
|
|
1478
|
+
# - every `pre_eval:` file outside `files` (a stat row, or an absence row) — the session tracks only analysed
|
|
1479
|
+
# files, and one that is analysed is guarded by its own row.
|
|
1480
|
+
#
|
|
1481
|
+
# Recomputing any of these on a recheck would vouch for the tree the recheck saw while its answer still
|
|
1482
|
+
# carries rows computed before the change, so a change to one keeps the probe declining until the next full
|
|
1483
|
+
# run. nil when this process analysed no file itself — a pool run, or a run with no files.
|
|
1484
|
+
#
|
|
1485
|
+
# Split by what identifies the input. `pinned` holds the signature files outside the project's own signature
|
|
1486
|
+
# roots, which the slot's key identifies already: the engine's own (`data/`, a bundled plugin's `sig/`, a
|
|
1487
|
+
# gem overlay) by the engine slot, a gem's `sig/` and an `rbs collection` directory by the lockfiles. `owned`
|
|
1488
|
+
# holds the rest, which only their rows identify. The session's write guard refuses an `owned` row whose
|
|
1489
|
+
# change time it cannot read, and passes over a `pinned` one.
|
|
1490
|
+
BaselineRows = Data.define(:owned, :pinned)
|
|
1491
|
+
|
|
1492
|
+
SignatureRoots = Struct.new(:signature_paths)
|
|
1493
|
+
private_constant :SignatureRoots
|
|
1494
|
+
|
|
1495
|
+
# @param files — the run's analysed-path set.
|
|
1496
|
+
def baseline_dependency_rows(files:)
|
|
1497
|
+
return nil if pool_mode? || !@collect_stats || @analyzed_files.empty?
|
|
1498
|
+
|
|
1499
|
+
own, pinned = signature_rows
|
|
1500
|
+
BaselineRows.new(
|
|
1501
|
+
owned: Cache::Descriptor.new(
|
|
1502
|
+
files: own.files + existence_entries(signature_root_candidates) +
|
|
1503
|
+
discovery_file_entries({ files: files }) + unanalysed_pre_eval_entries(files),
|
|
1504
|
+
globs: (own.globs + discovery_glob_entries).uniq
|
|
1505
|
+
),
|
|
1506
|
+
pinned: pinned
|
|
1507
|
+
)
|
|
1508
|
+
end
|
|
1509
|
+
|
|
1510
|
+
# The signature tree's rows, the project's own and the rest.
|
|
1511
|
+
def signature_rows
|
|
1512
|
+
roots = SignatureRoots.new(@snapshots.signature_paths)
|
|
1513
|
+
own = signature_root_candidates.map { |root| File.join(File.absolute_path(root.to_s), "") }
|
|
1514
|
+
own_files, other_files = Cache::RbsDescriptor.file_entries(roots, comparator: :stat)
|
|
1515
|
+
.partition { |row| within?(row.path, own) }
|
|
1516
|
+
own_globs, other_globs = Cache::RbsDescriptor.glob_entries(roots).partition { |row| within?(row.root, own) }
|
|
1517
|
+
[Cache::Descriptor.new(files: own_files, globs: own_globs.uniq),
|
|
1518
|
+
Cache::Descriptor.new(files: other_files, globs: other_globs.uniq)]
|
|
1519
|
+
end
|
|
1520
|
+
|
|
1521
|
+
def within?(path, roots)
|
|
1522
|
+
directory = File.join(File.absolute_path(path.to_s), "")
|
|
1523
|
+
roots.any? { |root| directory.start_with?(root) }
|
|
1524
|
+
end
|
|
1525
|
+
|
|
1526
|
+
# The project's own signature roots, whether or not they exist: the configured `signature_paths:`, or the
|
|
1527
|
+
# `sig` directory `Environment.default_signature_paths` auto-detects when none are configured. The
|
|
1528
|
+
# signature rows' listing rows cover a file appearing under a root that existed; only an existence row
|
|
1529
|
+
# covers the root itself appearing.
|
|
1530
|
+
def signature_root_candidates
|
|
1531
|
+
@configuration.signature_paths || ["sig"]
|
|
1532
|
+
end
|
|
1533
|
+
|
|
1534
|
+
# One names-mode listing row per directory the discovery widening globbed, so a file appearing there is seen
|
|
1535
|
+
# as well as an edit to one that existed.
|
|
1536
|
+
def discovery_glob_entries
|
|
1537
|
+
return [] if @project_discovery_expansion.nil?
|
|
1538
|
+
|
|
1539
|
+
@configuration.paths.select { |root| File.directory?(root) }.map do |root|
|
|
1540
|
+
Cache::Descriptor::GlobEntry.compute(root: root, pattern: PathExpansion::RUBY_GLOB, mode: :names)
|
|
1541
|
+
end
|
|
1542
|
+
end
|
|
1543
|
+
|
|
1544
|
+
def unanalysed_pre_eval_entries(files)
|
|
1545
|
+
analysed = files.to_set { |path| File.absolute_path(path) }
|
|
1546
|
+
outside = @configuration.pre_eval.reject { |path| analysed.include?(File.absolute_path(path)) }
|
|
1547
|
+
outside.map do |path|
|
|
1548
|
+
if File.file?(path)
|
|
1549
|
+
Cache::Descriptor::FileEntry.stat(path: path, digest: Cache::FileDigest.hexdigest(path))
|
|
1550
|
+
else
|
|
1551
|
+
existence_entries([path]).first
|
|
1238
1552
|
end
|
|
1239
1553
|
end
|
|
1240
|
-
Cache::Descriptor.new(files: entries, globs: globs.uniq)
|
|
1241
1554
|
end
|
|
1242
1555
|
|
|
1556
|
+
def existence_entries(paths)
|
|
1557
|
+
paths.uniq.map do |path|
|
|
1558
|
+
physical = @buffer ? @buffer.resolve(path) : path
|
|
1559
|
+
if File.exist?(physical)
|
|
1560
|
+
Cache::Descriptor::FileEntry.present(path: physical)
|
|
1561
|
+
else
|
|
1562
|
+
Cache::Descriptor::FileEntry.absent(path: physical)
|
|
1563
|
+
end
|
|
1564
|
+
end
|
|
1565
|
+
end
|
|
1566
|
+
private :plugin_boundary_rows, :producer_watch_glob_entries, :existence_entries,
|
|
1567
|
+
:analysis_reads_attributed?, :signature_root_candidates, :discovery_glob_entries,
|
|
1568
|
+
:unanalysed_pre_eval_entries, :signature_rows, :within?
|
|
1569
|
+
|
|
1243
1570
|
# Issue #684 — the project files this run DISCOVERED but did not analyse. A widened run's diagnostics
|
|
1244
1571
|
# are a function of them (that is the entire point: the method whose absence would have been reported
|
|
1245
1572
|
# is defined in one of them), so the run-result cache must invalidate when one changes. Empty on every
|
|
@@ -1268,6 +1595,30 @@ module Rigor
|
|
|
1268
1595
|
# expansion — so without this entry, editing one would leave the run-result cache serving diagnostics
|
|
1269
1596
|
# computed against the previous version. The common case (a `lib/core_ext/` file that is also under
|
|
1270
1597
|
# `paths:`) contributes a duplicate entry, which the descriptor's per-path validation absorbs.
|
|
1598
|
+
# #392 — the TEMPLATE files behind the run's units. The key slot carries the compiled Ruby's digest
|
|
1599
|
+
# (`template_units_digest`), which is what decides whether a cached answer describes this source; these
|
|
1600
|
+
# entries are the validation half, so a template edited between two runs re-globs and re-digests here
|
|
1601
|
+
# exactly as an analysed `.rb` file does. Both are needed: a transform that compiles two different
|
|
1602
|
+
# templates to the same Ruby must still not resurrect the wrong PATH in a diagnostic.
|
|
1603
|
+
# #392 — one `:names` row per claimed `template_globs:` pattern, whether or not it matched. The file
|
|
1604
|
+
# rows below cover edits to templates that EXIST; only a glob row notices one appearing or vanishing,
|
|
1605
|
+
# which is what let a project's first template stay invisible to every warm run.
|
|
1606
|
+
def template_unit_glob_entries
|
|
1607
|
+
template_units.glob_entries
|
|
1608
|
+
end
|
|
1609
|
+
|
|
1610
|
+
# Every template the run READ, successes and failures alike: a `plugin_loader` row produced by a
|
|
1611
|
+
# template that would not compile must not outlive the edit that fixes it, and a buffer-bound path is
|
|
1612
|
+
# digested at the bytes the run actually read.
|
|
1613
|
+
def template_unit_file_entries
|
|
1614
|
+
template_units.source_paths.filter_map do |path|
|
|
1615
|
+
physical = template_units.physical_path(path, @buffer)
|
|
1616
|
+
next unless File.file?(physical)
|
|
1617
|
+
|
|
1618
|
+
Cache::Descriptor::FileEntry.stat(path: physical, digest: Cache::FileDigest.hexdigest(physical))
|
|
1619
|
+
end
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1271
1622
|
def pre_eval_file_entries
|
|
1272
1623
|
@configuration.pre_eval.filter_map do |path|
|
|
1273
1624
|
physical = @buffer ? @buffer.resolve(path) : path
|
|
@@ -1352,19 +1703,42 @@ module Rigor
|
|
|
1352
1703
|
@project_discovered_singleton_def_sources = discovery.discovered_singleton_def_sources
|
|
1353
1704
|
@project_discovered_superclasses = discovery.discovered_superclasses
|
|
1354
1705
|
@project_discovered_header_nestings = discovery.discovered_header_nestings
|
|
1355
|
-
|
|
1356
|
-
@
|
|
1706
|
+
apply_discovery_mixin_tables(discovery)
|
|
1707
|
+
@project_discovered_deferred_ranges = discovery.discovered_deferred_ranges
|
|
1708
|
+
apply_discovery_call_surface_tables(discovery)
|
|
1357
1709
|
@project_discovered_class_sources = discovery.discovered_class_sources
|
|
1358
1710
|
@project_constant_values = discovery.constant_values
|
|
1359
1711
|
@published_constant_name_set = nil
|
|
1712
|
+
@constant_writer_index = nil
|
|
1713
|
+
@constant_shadower_index = nil
|
|
1360
1714
|
@project_constant_sources = discovery.constant_sources
|
|
1361
1715
|
@project_constant_writes = discovery.constant_writes
|
|
1362
1716
|
@project_discovered_method_visibilities = discovery.discovered_method_visibilities
|
|
1363
1717
|
@project_discovered_methods = discovery.discovered_methods
|
|
1718
|
+
@project_discovered_parameter_envelopes = discovery.discovered_parameter_envelopes
|
|
1364
1719
|
@project_data_member_layouts = discovery.data_member_layouts
|
|
1365
1720
|
@project_struct_member_layouts = discovery.struct_member_layouts
|
|
1366
1721
|
end
|
|
1367
1722
|
|
|
1723
|
+
# The tables only check rules read beyond dispatch — the issue #1120 refinements and the issue #1367
|
|
1724
|
+
# global-write census — and the ADR-119 WD1 siblings of the paired members. Extracted to keep
|
|
1725
|
+
# {#apply_discovery_result} under its ABC budget.
|
|
1726
|
+
def apply_discovery_call_surface_tables(discovery)
|
|
1727
|
+
@project_discovery_siblings = discovery.siblings
|
|
1728
|
+
@project_discovered_refinements = discovery.discovered_refinements
|
|
1729
|
+
@project_discovered_global_write_census = discovery.discovered_global_write_census
|
|
1730
|
+
end
|
|
1731
|
+
|
|
1732
|
+
# The three mixin tables the discovery pass carries — the ADR-24 `include` map, its issue #1123
|
|
1733
|
+
# `prepend` twin, and the #898 `extend` table. Extracted to keep {#apply_discovery_result} under its
|
|
1734
|
+
# ABC budget.
|
|
1735
|
+
def apply_discovery_mixin_tables(discovery)
|
|
1736
|
+
@project_discovered_includes = discovery.discovered_includes
|
|
1737
|
+
@project_discovered_prepends = discovery.discovered_prepends
|
|
1738
|
+
@project_discovered_extends = discovery.discovered_extends
|
|
1739
|
+
@project_unpositioned_mixins = discovery.unpositioned_mixins
|
|
1740
|
+
end
|
|
1741
|
+
|
|
1368
1742
|
# Internal: builds the deferred cross-file discovery tables at most once per run and adopts them.
|
|
1369
1743
|
# Memoised on `@project_discovery_done` (reset at the start of `#run`). No-op under `@prebuilt` — the
|
|
1370
1744
|
# LSP path deliberately seeds an empty project scope from a snapshot that carries no discovery tables,
|
|
@@ -1379,7 +1753,7 @@ module Rigor
|
|
|
1379
1753
|
|
|
1380
1754
|
@project_discovery_done = true
|
|
1381
1755
|
# Issue #684 — the cross-file index spans the PROJECT even when the analysis targets a file list.
|
|
1382
|
-
expansion = @project_discovery_expansion || expansion
|
|
1756
|
+
expansion = @project_discovery_expansion || once_each(expansion)
|
|
1383
1757
|
if @collect_seed_bundles
|
|
1384
1758
|
# ADR-85 WD2 — rebuild discovery from the prior run's bundles (re-walking only changed files) and
|
|
1385
1759
|
# capture the refreshed bundle set for the session to persist.
|
|
@@ -1413,11 +1787,42 @@ module Rigor
|
|
|
1413
1787
|
# Excluded, each because its expansion is already the one it means: the LSP `prebuilt:` path (which
|
|
1414
1788
|
# seeds discovery from a snapshot), editor `buffer:` mode, `run_source`'s in-memory single file, and
|
|
1415
1789
|
# ADR-46's `analyze_only` subset mode (which already passes the full expansion and filters later).
|
|
1790
|
+
#
|
|
1791
|
+
# Issue #1556 — the widened set holds each FILE once. `.rigor.yml`'s `paths:` load as absolute paths and a
|
|
1792
|
+
# path argument usually does not, so `rigor check lib` widened discovery over both spellings of every file:
|
|
1793
|
+
# the pre-pass then saw two writers for every constant a file declares and withheld its publication (#644),
|
|
1794
|
+
# and every file was a discovered-not-analysed dependency of the run's cache entry.
|
|
1795
|
+
#
|
|
1796
|
+
# The widening decision itself still compares the paths as spelled. `exclude:` patterns match a path as it
|
|
1797
|
+
# is spelled, so until #1576 is fixed the configured absolute `lib` can discover files the argument `lib`
|
|
1798
|
+
# excludes (a relative `lib/gen/**` pattern never matches `/abs/lib/gen/x.rb`), and treating the two as the
|
|
1799
|
+
# same root would drop those files from discovery.
|
|
1416
1800
|
def project_discovery_expansion(paths, expansion)
|
|
1417
1801
|
return nil unless widen_discovery_to_project?(paths)
|
|
1418
1802
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1803
|
+
analysed = expansion.fetch(:files)
|
|
1804
|
+
files = discovery_files(expand_paths(@configuration.paths | paths).fetch(:files), analysed)
|
|
1805
|
+
# Overlapping arguments (`lib lib/user.rb`) name an analysed file twice; the widened set names it once.
|
|
1806
|
+
files.size > analysed.uniq { |path| File.absolute_path(path) }.size ? { files: files, errors: [] } : nil
|
|
1807
|
+
end
|
|
1808
|
+
|
|
1809
|
+
# The expansion with each file once, first spelling kept: overlapping arguments (`lib ./lib`) that do not
|
|
1810
|
+
# widen discovery still name a file twice, and the pre-pass would take every constant in it for one written
|
|
1811
|
+
# twice (#1556). Symlinked aliases (`src -> lib`) are not folded; that needs a realpath and stays out of scope.
|
|
1812
|
+
def once_each(expansion)
|
|
1813
|
+
files = expansion.fetch(:files)
|
|
1814
|
+
unique = files.uniq { |path| File.absolute_path(path) }
|
|
1815
|
+
unique.size == files.size ? expansion : expansion.merge(files: unique)
|
|
1816
|
+
end
|
|
1817
|
+
|
|
1818
|
+
# Each file once, in project order, under the spelling the analysis uses when it is analysed.
|
|
1819
|
+
def discovery_files(project_files, analysed_files)
|
|
1820
|
+
spelling = analysed_files.to_h { |path| [File.absolute_path(path), path] }
|
|
1821
|
+
seen = Set.new
|
|
1822
|
+
project_files.filter_map do |path|
|
|
1823
|
+
absolute = File.absolute_path(path)
|
|
1824
|
+
spelling.fetch(absolute, path) if seen.add?(absolute)
|
|
1825
|
+
end
|
|
1421
1826
|
end
|
|
1422
1827
|
|
|
1423
1828
|
def widen_discovery_to_project?(paths)
|
|
@@ -1436,7 +1841,7 @@ module Rigor
|
|
|
1436
1841
|
|
|
1437
1842
|
private :run_project_pre_passes, :adopt_prebuilt_project_scan, :apply_pre_passes_result,
|
|
1438
1843
|
:apply_discovery_result, :ensure_project_discovery, :force_eager_discovery?,
|
|
1439
|
-
:project_discovery_expansion, :widen_discovery_to_project?
|
|
1844
|
+
:project_discovery_expansion, :discovery_files, :once_each, :widen_discovery_to_project?
|
|
1440
1845
|
|
|
1441
1846
|
# Ruby versions probed (ascending) to discover the lowest one this Prism build accepts for
|
|
1442
1847
|
# `version:`. Prism exposes no version list, so the floor is found empirically — only when a
|
|
@@ -1498,6 +1903,25 @@ module Rigor
|
|
|
1498
1903
|
# The buffer's logical path is added to the file list even if it's not under `paths:` — per design §
|
|
1499
1904
|
# "Failure envelope": "--instead-of=Y with Y not under any paths: directory → treated as a valid
|
|
1500
1905
|
# logical identity for the buffer".
|
|
1906
|
+
# #392 — the run's template units. Built once per run, on the parent, AFTER the plugin pre-pass has
|
|
1907
|
+
# loaded the registry; a run whose plugins declare no `template_globs:` never globs and never calls a
|
|
1908
|
+
# plugin.
|
|
1909
|
+
#
|
|
1910
|
+
# #1038 — a runner built with `prebuilt:` hands the snapshot's warm index to `collect(previous:)`, so
|
|
1911
|
+
# a per-buffer publish against a long-lived {LanguageServer::ProjectContext} re-runs the transform
|
|
1912
|
+
# only for a template that actually moved on disk (and for the buffer's own, always). The globs are
|
|
1913
|
+
# still re-expanded and every surviving template is still revalidated, so an added, deleted or edited
|
|
1914
|
+
# template is seen on the next publish without the owner invalidating anything. A runner with no
|
|
1915
|
+
# `prebuilt:` — every CLI run — passes no `previous:` and builds the index exactly as before.
|
|
1916
|
+
def template_units
|
|
1917
|
+
@template_units ||= if @plugin_registry.nil?
|
|
1918
|
+
TemplateUnits.empty
|
|
1919
|
+
else
|
|
1920
|
+
TemplateUnits.collect(registry: @plugin_registry, buffer: @buffer,
|
|
1921
|
+
previous: @prebuilt&.template_units)
|
|
1922
|
+
end
|
|
1923
|
+
end
|
|
1924
|
+
|
|
1501
1925
|
def target_files(expansion)
|
|
1502
1926
|
files = expansion.fetch(:files)
|
|
1503
1927
|
# ADR-46 slice 2 — restrict the analyzed set to the affected closure while the pre-pass (run
|
|
@@ -1508,14 +1932,41 @@ module Rigor
|
|
|
1508
1932
|
# the same allowance option A makes below.
|
|
1509
1933
|
files = files.select { |path| @analyze_only.include?(path) }
|
|
1510
1934
|
files |= [@buffer.logical_path] if @buffer && @analyze_only.include?(@buffer.logical_path)
|
|
1511
|
-
return files
|
|
1935
|
+
return template_unit_targets(files)
|
|
1512
1936
|
end
|
|
1513
|
-
return files if @buffer.nil?
|
|
1937
|
+
return template_unit_targets(files) if @buffer.nil?
|
|
1514
1938
|
|
|
1515
|
-
# Editor mode option A — no closure, so the buffer's single logical path IS the analyzed set.
|
|
1939
|
+
# Editor mode option A — no closure, so the buffer's single logical path IS the analyzed set. The
|
|
1940
|
+
# template units deliberately do NOT join it: a per-buffer publish answers about the buffer the
|
|
1941
|
+
# editor is showing, and appending every view would publish diagnostics for files the editor did
|
|
1942
|
+
# not ask about (and re-parse them on every keystroke).
|
|
1516
1943
|
[@buffer.logical_path]
|
|
1517
1944
|
end
|
|
1518
1945
|
|
|
1946
|
+
# #392 — the template units join the analysed set AFTER any narrowing, so an `--incremental` recheck
|
|
1947
|
+
# re-analyses every unit even when its closure is one `.rb` file. Nothing in the ADR-46 dependency
|
|
1948
|
+
# graph names a synthesised file, so there is no edge that could put a unit in a closure — narrowing
|
|
1949
|
+
# first and appending second is what makes "always re-analysed" true rather than a comment. The cost
|
|
1950
|
+
# is one parse per template on the warm path; the alternative silently drops every template
|
|
1951
|
+
# diagnostic from the second run onwards.
|
|
1952
|
+
#
|
|
1953
|
+
# Appended rather than merged in sorted order so the `.rb` expansion's own order — and every output
|
|
1954
|
+
# that follows it — is byte-identical for a project with no units.
|
|
1955
|
+
def template_unit_targets(files)
|
|
1956
|
+
return files if template_units.empty?
|
|
1957
|
+
|
|
1958
|
+
files + (template_units.paths - files)
|
|
1959
|
+
end
|
|
1960
|
+
private :template_unit_targets
|
|
1961
|
+
|
|
1962
|
+
# #392 — the unit paths this run synthesised. Read by {IncrementalSession}, which keeps them OUT of
|
|
1963
|
+
# its analysed-file set: a unit is never served from the per-file cache and never counts as a file
|
|
1964
|
+
# that was removed from the project, because `#target_files` re-analyses it every run.
|
|
1965
|
+
def template_unit_paths
|
|
1966
|
+
template_units.paths
|
|
1967
|
+
end
|
|
1968
|
+
public :template_unit_paths
|
|
1969
|
+
|
|
1519
1970
|
# Editor mode (`buffer:` non-nil) auto-flips the cache store to `read_only: true` so multiple
|
|
1520
1971
|
# debounced editor invocations against the same project don't churn the on-disk cache or race on
|
|
1521
1972
|
# schema-version writes. Cache reads continue unchanged; misses still run the producer block but the
|
|
@@ -1552,6 +2003,7 @@ module Rigor
|
|
|
1552
2003
|
synthetic_method_index: -> { @synthetic_method_index },
|
|
1553
2004
|
project_patched_methods: -> { @project_patched_methods },
|
|
1554
2005
|
project_scope_seed: -> { project_scope_seed_tables },
|
|
2006
|
+
template_units: -> { template_units },
|
|
1555
2007
|
analyze_file: ->(path, environment) { analyze_file(path, environment) },
|
|
1556
2008
|
restored_run_level_rows: @restored_run_level_rows
|
|
1557
2009
|
)
|
|
@@ -1571,7 +2023,8 @@ module Rigor
|
|
|
1571
2023
|
env_build_failure_snapshot: -> { @snapshots.env_build_failure },
|
|
1572
2024
|
definition_build_failures_snapshot: -> { @snapshots.definition_build_failures },
|
|
1573
2025
|
hkt_scan_failure_snapshot: -> { @snapshots.hkt_scan_failure },
|
|
1574
|
-
conformance_results_snapshot: -> { @snapshots.conformance_results }
|
|
2026
|
+
conformance_results_snapshot: -> { @snapshots.conformance_results },
|
|
2027
|
+
pooled_run_disclosures: -> { @pool_coordinator.collected_run_disclosures }
|
|
1575
2028
|
)
|
|
1576
2029
|
end
|
|
1577
2030
|
|
|
@@ -1598,7 +2051,9 @@ module Rigor
|
|
|
1598
2051
|
RunStats.new(
|
|
1599
2052
|
wall_seconds: Process.clock_gettime(Process::CLOCK_MONOTONIC) - wall_started_at,
|
|
1600
2053
|
peak_rss_bytes: RunStats.peak_rss_bytes,
|
|
1601
|
-
|
|
2054
|
+
# #392 — the template units count too: they are files this run parsed and typed, and a stats line
|
|
2055
|
+
# that omitted them would under-report exactly the work a views-heavy project added.
|
|
2056
|
+
target_files: expansion.fetch(:files).size + template_units.paths.size,
|
|
1602
2057
|
rbs_classes_total: snapshot.size,
|
|
1603
2058
|
rbs_classes_project_sig: project_sig,
|
|
1604
2059
|
rbs_classes_bundled: bundled,
|
|
@@ -1649,7 +2104,7 @@ module Rigor
|
|
|
1649
2104
|
end
|
|
1650
2105
|
|
|
1651
2106
|
def shadow_rule_walk?
|
|
1652
|
-
|
|
2107
|
+
ShadowHarness.enabled?
|
|
1653
2108
|
end
|
|
1654
2109
|
|
|
1655
2110
|
def collect_plugin_diagnostics(plugin, path, root, scope, node_result)
|
|
@@ -1755,6 +2210,12 @@ module Rigor
|
|
|
1755
2210
|
# so Prism's location data carries the LOGICAL path. Non-binding paths go through the cheaper
|
|
1756
2211
|
# `Prism.parse_file` codepath unchanged.
|
|
1757
2212
|
def parse_source(path)
|
|
2213
|
+
entry = template_units[path]
|
|
2214
|
+
if entry
|
|
2215
|
+
return Prism.parse(entry.source, filepath: path, version: @configuration.target_ruby,
|
|
2216
|
+
scopes: template_units.parse_scopes(path))
|
|
2217
|
+
end
|
|
2218
|
+
|
|
1758
2219
|
if @in_memory_sources&.key?(path)
|
|
1759
2220
|
return Prism.parse(@in_memory_sources[path], filepath: path, version: @configuration.target_ruby)
|
|
1760
2221
|
end
|
|
@@ -1788,13 +2249,9 @@ module Rigor
|
|
|
1788
2249
|
# bucket identically to the sequential path.
|
|
1789
2250
|
tables[:run_generation] = @run_generation if @run_generation
|
|
1790
2251
|
tables[:discovered_classes] = @project_discovered_classes unless @project_discovered_classes.empty?
|
|
1791
|
-
tables[:discovered_def_nodes] = @project_discovered_def_nodes unless @project_discovered_def_nodes.empty?
|
|
1792
2252
|
unless @project_discovered_def_nestings.empty?
|
|
1793
2253
|
tables[:discovered_def_nestings] = @project_discovered_def_nestings
|
|
1794
2254
|
end
|
|
1795
|
-
unless @project_discovered_singleton_def_nodes.empty?
|
|
1796
|
-
tables[:discovered_singleton_def_nodes] = @project_discovered_singleton_def_nodes
|
|
1797
|
-
end
|
|
1798
2255
|
seed_def_source_tables(tables)
|
|
1799
2256
|
unless @project_discovered_superclasses.empty?
|
|
1800
2257
|
tables[:discovered_superclasses] = @project_discovered_superclasses
|
|
@@ -1803,33 +2260,77 @@ module Rigor
|
|
|
1803
2260
|
tables[:discovered_header_nestings] = @project_discovered_header_nestings
|
|
1804
2261
|
end
|
|
1805
2262
|
seed_mixin_tables(tables)
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
end
|
|
1809
|
-
tables[:discovered_methods] = @project_discovered_methods unless @project_discovered_methods.empty?
|
|
2263
|
+
seed_paired_tables(tables)
|
|
2264
|
+
seed_call_surface_tables(tables)
|
|
1810
2265
|
seed_opt_in_pre_pass_tables(tables)
|
|
1811
2266
|
seed_member_layout_tables(tables)
|
|
1812
2267
|
seed_dependency_attribution_tables(tables)
|
|
1813
|
-
|
|
2268
|
+
# Only a base seed can bring in half a pair (`seed_paired_tables` writes both halves), so a run without
|
|
2269
|
+
# one skips the copy this costs per file.
|
|
2270
|
+
@discovery_seed ? Scope::DiscoveryIndex.compact_pairs(tables) : tables
|
|
2271
|
+
end
|
|
2272
|
+
|
|
2273
|
+
# ADR-119 WD1 — the members `Scope::DiscoveryIndex::SIBLINGS` pairs, each with its sibling. A pair rides the
|
|
2274
|
+
# seed when either half is non-empty and is left out when both are, which keeps an `discovery_seed:` base's
|
|
2275
|
+
# table for a pair this run computed nothing for. {Scope::DiscoveryIndex#with} takes the two together, and
|
|
2276
|
+
# `compact_pairs` completes a half pair a base seed brought in.
|
|
2277
|
+
PAIRED_MEMBER_IVARS = Scope::DiscoveryIndex::SIBLINGS.keys.to_h do |member|
|
|
2278
|
+
[member, :"@project_#{member}"]
|
|
2279
|
+
end.freeze
|
|
2280
|
+
private_constant :PAIRED_MEMBER_IVARS
|
|
2281
|
+
|
|
2282
|
+
def seed_paired_tables(tables)
|
|
2283
|
+
PAIRED_MEMBER_IVARS.each do |member, ivar|
|
|
2284
|
+
sibling = Scope::DiscoveryIndex::SIBLINGS.fetch(member)
|
|
2285
|
+
table = instance_variable_get(ivar)
|
|
2286
|
+
own = @project_discovery_siblings.fetch(sibling)
|
|
2287
|
+
next if table.empty? && own.empty?
|
|
2288
|
+
|
|
2289
|
+
tables[member] = table
|
|
2290
|
+
tables[sibling] = own
|
|
2291
|
+
end
|
|
2292
|
+
end
|
|
2293
|
+
|
|
2294
|
+
# The tables only check rules read: the issue #1120 refinements (`call.undefined-method`) and the issue #1367
|
|
2295
|
+
# global-write census (the `global.*` write rules); the issue #992 envelopes ride {#seed_paired_tables}. Split
|
|
2296
|
+
# out of {#project_scope_seed_tables} to keep it under the complexity budget.
|
|
2297
|
+
def seed_call_surface_tables(tables)
|
|
2298
|
+
tables[:discovered_refinements] = @project_discovered_refinements unless @project_discovered_refinements.empty?
|
|
2299
|
+
# Issue #1367 — read by the `global.*` write rules.
|
|
2300
|
+
return if @project_discovered_global_write_census.empty?
|
|
2301
|
+
|
|
2302
|
+
tables[:discovered_global_write_census] = @project_discovered_global_write_census
|
|
1814
2303
|
end
|
|
1815
2304
|
|
|
1816
|
-
# The
|
|
1817
|
-
#
|
|
1818
|
-
#
|
|
2305
|
+
# The three mixin tables: the ADR-24 instance-side `include` map, its issue #1123 `prepend` twin
|
|
2306
|
+
# (which carries the instance-ancestor order the include table's set-shaped value cannot), and the
|
|
2307
|
+
# #898 singleton-side `extend` table. Grouped here because they are read as one picture of a class's
|
|
2308
|
+
# ancestry, and split out of {#project_scope_seed_tables} to keep it under the complexity budget.
|
|
1819
2309
|
def seed_mixin_tables(tables)
|
|
1820
2310
|
tables[:discovered_includes] = @project_discovered_includes unless @project_discovered_includes.empty?
|
|
2311
|
+
tables[:discovered_prepends] = @project_discovered_prepends unless @project_discovered_prepends.empty?
|
|
1821
2312
|
tables[:discovered_extends] = @project_discovered_extends unless @project_discovered_extends.empty?
|
|
2313
|
+
tables[:unpositioned_mixins] = @project_unpositioned_mixins unless @project_unpositioned_mixins.empty?
|
|
1822
2314
|
end
|
|
1823
2315
|
|
|
1824
|
-
# ADR-46 slice 1 / issue #644 — the two SOURCE-ATTRIBUTION tables, read
|
|
2316
|
+
# ADR-46 slice 1 / issue #644 — the two SOURCE-ATTRIBUTION tables, read by the recording accessors
|
|
1825
2317
|
# (`Scope#record_class_dependency` for the class-declaration map, `Scope#record_constant_dependency`
|
|
1826
|
-
# for the constant-write map).
|
|
1827
|
-
#
|
|
2318
|
+
# for the constant-write map). The class-declaration map rides every run: an ancestor-order reader also
|
|
2319
|
+
# asks whether a class is declared in more than one file. The constant-write map rides only a recording
|
|
2320
|
+
# run. Extracted to keep {#project_scope_seed_tables} under the complexity budget.
|
|
2321
|
+
#
|
|
2322
|
+
# Issue #617 — the census's binding writes, grouped by last segment, ride every run instead: a constant
|
|
2323
|
+
# compound write whose plain read resolves nothing reads its binding off them, a TYPE rather than an edge.
|
|
2324
|
+
# Issue #1290's shadowing writes ride beside them for the lexical ladder, for the same reason.
|
|
1828
2325
|
def seed_dependency_attribution_tables(tables)
|
|
1829
|
-
|
|
1830
|
-
|
|
2326
|
+
writers = constant_writer_index
|
|
2327
|
+
tables[:constant_writers] = writers unless writers.empty?
|
|
2328
|
+
shadowers = constant_shadower_index
|
|
2329
|
+
tables[:constant_shadowers] = shadowers unless shadowers.empty?
|
|
1831
2330
|
tables[:discovered_class_sources] = @project_discovered_class_sources unless
|
|
1832
2331
|
@project_discovered_class_sources.empty?
|
|
2332
|
+
return unless @record_dependencies
|
|
2333
|
+
|
|
1833
2334
|
tables[:constant_sources] = @project_constant_sources unless @project_constant_sources.empty?
|
|
1834
2335
|
end
|
|
1835
2336
|
|
|
@@ -1860,6 +2361,44 @@ module Rigor
|
|
|
1860
2361
|
tables[:published_constant_names] = names unless names.empty?
|
|
1861
2362
|
end
|
|
1862
2363
|
|
|
2364
|
+
# Issue #617 — the censused names that bind, grouped by last segment (`Scope#bound_constant_names`). A
|
|
2365
|
+
# name some write other than a memo `||=` touches always binds. A memo-only name binds once memos of its
|
|
2366
|
+
# segment span two files: either may load first and set the constant the other then reads, so only a
|
|
2367
|
+
# memo no other file shares keeps the idiom's reading. Files are counted expanded, because discovery can
|
|
2368
|
+
# walk one file under two spellings (`lib/b.rb`, `./lib/b.rb`).
|
|
2369
|
+
def constant_writer_index
|
|
2370
|
+
@constant_writer_index ||= begin
|
|
2371
|
+
memo_files = memo_files_by_segment
|
|
2372
|
+
@project_constant_writes.each_with_object({}) do |(name, by_path), index|
|
|
2373
|
+
segment = name.split("::").last
|
|
2374
|
+
next if memo_only?(by_path) && memo_files.fetch(segment).size < 2
|
|
2375
|
+
|
|
2376
|
+
(index[segment] ||= []) << name
|
|
2377
|
+
end.each_value(&:freeze).freeze
|
|
2378
|
+
end
|
|
2379
|
+
end
|
|
2380
|
+
|
|
2381
|
+
# Issue #1290 — the censused names some write other than a memo `||=` assigns, grouped by last segment
|
|
2382
|
+
# (`Scope#shadowing_constant_names`): the ones that exist whether or not an outer constant of the name
|
|
2383
|
+
# does, and so stop the lexical ladder above it. A memo-only name is left out however many files memoize
|
|
2384
|
+
# it, unlike in {#constant_writer_index}: `X ||= v` resolves `X` through the very lookup whose lower rung
|
|
2385
|
+
# the stop would replace, so wherever that rung answers, the memo finds it and never creates the name.
|
|
2386
|
+
def constant_shadower_index
|
|
2387
|
+
@constant_shadower_index ||= @project_constant_writes.each_with_object({}) do |(name, by_path), index|
|
|
2388
|
+
(index[name.split("::").last] ||= []) << name unless memo_only?(by_path)
|
|
2389
|
+
end.each_value(&:freeze).freeze
|
|
2390
|
+
end
|
|
2391
|
+
|
|
2392
|
+
def memo_files_by_segment
|
|
2393
|
+
@project_constant_writes.each_with_object({}) do |(name, by_path), files|
|
|
2394
|
+
set = (files[name.split("::").last] ||= Set.new)
|
|
2395
|
+
by_path.each { |path, descriptor| set << File.absolute_path(path) if memo_descriptor?(descriptor) }
|
|
2396
|
+
end
|
|
2397
|
+
end
|
|
2398
|
+
|
|
2399
|
+
def memo_only?(by_path) = by_path.each_value.all? { |descriptor| memo_descriptor?(descriptor) }
|
|
2400
|
+
def memo_descriptor?(descriptor) = descriptor == Inference::ScopeIndexer::CONSTANT_MEMO
|
|
2401
|
+
|
|
1863
2402
|
# Issue #644 — the LAST SEGMENTS of the published table, the run-wide half of
|
|
1864
2403
|
# `Scope#published_constant?`. Seeded on EVERY run (unlike `constant_sources`, which only a recording
|
|
1865
2404
|
# run needs) because {CheckRules::PublishedConstantGuard} asks the question on every `if` / `unless`.
|
|
@@ -1911,14 +2450,14 @@ module Rigor
|
|
|
1911
2450
|
writers = @project_constant_sources[name]
|
|
1912
2451
|
wildcard = @project_constant_sources["#{Inference::ScopeIndexer::DYNAMIC_TARGET_PREFIX}#{segment}"]
|
|
1913
2452
|
[writers, wildcard].any? do |paths|
|
|
1914
|
-
paths&.any? { |path| !listed.include?(File.
|
|
2453
|
+
paths&.any? { |path| !listed.include?(File.absolute_path(path)) }
|
|
1915
2454
|
end
|
|
1916
2455
|
end
|
|
1917
2456
|
|
|
1918
2457
|
# The listed `pre_eval:` files, expanded so a census path and a configured one compare as the same
|
|
1919
2458
|
# file whichever spelling each arrived in.
|
|
1920
2459
|
def pre_eval_path_set
|
|
1921
|
-
@pre_eval_path_set ||= @configuration.pre_eval.to_set { |path| File.
|
|
2460
|
+
@pre_eval_path_set ||= @configuration.pre_eval.to_set { |path| File.absolute_path(path) }
|
|
1922
2461
|
end
|
|
1923
2462
|
|
|
1924
2463
|
# ADR-46 — seed the instance + singleton `"path:line"` def-source tables (each only when non-empty).
|
|
@@ -1948,11 +2487,18 @@ module Rigor
|
|
|
1948
2487
|
def analyze_file(path, environment)
|
|
1949
2488
|
return analyze_with_effects(path, environment) unless @record_dependencies
|
|
1950
2489
|
|
|
2490
|
+
# ADR-45 WD2 (#1507) — every plugin read this file's analysis makes is attributed to it, so the
|
|
2491
|
+
# incremental session can validate them on a later run that serves this file's answer from cache.
|
|
2492
|
+
@pre_analysis_plugin_rows ||= plugin_boundary_rows
|
|
2493
|
+
reads = []
|
|
1951
2494
|
diagnostics = nil
|
|
1952
|
-
record =
|
|
1953
|
-
|
|
2495
|
+
record = Plugin::IoBoundary.attributing(reads) do
|
|
2496
|
+
DependencyRecorder.record_for(path) do
|
|
2497
|
+
diagnostics = analyze_with_effects(path, environment)
|
|
2498
|
+
end
|
|
1954
2499
|
end
|
|
1955
2500
|
@file_dependencies[path] = record
|
|
2501
|
+
@analysis_reads[path] = reads unless reads.empty?
|
|
1956
2502
|
diagnostics
|
|
1957
2503
|
end
|
|
1958
2504
|
|
|
@@ -1963,10 +2509,11 @@ module Rigor
|
|
|
1963
2509
|
def analyze_with_effects(path, environment)
|
|
1964
2510
|
return analyze_file_body(path, environment) unless @record_effects
|
|
1965
2511
|
|
|
2512
|
+
entry = template_units[path]
|
|
1966
2513
|
diagnostics = nil
|
|
1967
2514
|
collection = Effects::Collector.collect_for(
|
|
1968
2515
|
path, attribution: @effect_attribution, envelopes: effect_envelope_index(environment),
|
|
1969
|
-
plugin_facts: effect_plugin_facts
|
|
2516
|
+
plugin_facts: effect_plugin_facts, unit_key: entry&.unit_key, unit_owner: entry&.self_type
|
|
1970
2517
|
) do
|
|
1971
2518
|
diagnostics = analyze_file_body(path, environment)
|
|
1972
2519
|
end
|
|
@@ -2005,7 +2552,9 @@ module Rigor
|
|
|
2005
2552
|
# is a per-FILE site, so one `Thread.current` read is already nothing. The per-dispatch site in
|
|
2006
2553
|
# `ExpressionTyper#call_type_for` is the one that pays for the integer fast path.
|
|
2007
2554
|
Effects::Collector.record_root(parse_result.value)
|
|
2008
|
-
scope =
|
|
2555
|
+
scope = template_units.seed(
|
|
2556
|
+
seed_project_scope(Scope.empty(environment: environment, source_path: path)), path
|
|
2557
|
+
)
|
|
2009
2558
|
# ADR-24 slice 4a/4 — record unresolved implicit-self calls during the typing pass ONLY (not
|
|
2010
2559
|
# CheckRules, whose own `type_of` queries would otherwise re-trigger the choke-point).
|
|
2011
2560
|
# `self_call_misses` feeds the `call.self-undefined-method` collector; the recorder is inert unless
|
|
@@ -2045,7 +2594,7 @@ module Rigor
|
|
|
2045
2594
|
# output and the plugin diagnostics are then built from that single walk's results.
|
|
2046
2595
|
def rule_and_plugin_diagnostics(path, parse_result, scope, index, self_call_misses)
|
|
2047
2596
|
root = parse_result.value
|
|
2048
|
-
node_collectors = CheckRules.build_node_collectors(path, index)
|
|
2597
|
+
node_collectors = CheckRules.build_node_collectors(path, index, root)
|
|
2049
2598
|
node_results = node_rule_results_by_plugin(path, root, scope, node_collectors, index)
|
|
2050
2599
|
diagnostics = CheckRules.diagnose(
|
|
2051
2600
|
path: path,
|