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
|
@@ -35,10 +35,17 @@ module Rigor
|
|
|
35
35
|
# silent wrong answer for a silent absent one at no gain (the same reasoning as
|
|
36
36
|
# {.toplevel_first_constant_type}'s caller-derived rungs).
|
|
37
37
|
def resolve_constant_path_name(name, scope, &known)
|
|
38
|
+
walk_constant_path(name, scope, known, caller_derived: true)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# {.resolve_constant_path_name}'s walk. `caller_derived: false` is {.resolve_constant_type}'s flag: the
|
|
42
|
+
# head skips the enclosing class's ancestors — the one step a recorded top-level body takes from its
|
|
43
|
+
# caller — while every later segment still walks its owner's ancestors, as Ruby does.
|
|
44
|
+
def walk_constant_path(name, scope, known, caller_derived:)
|
|
38
45
|
segments = name.to_s.split("::")
|
|
39
46
|
return nil unless segments.size > 1
|
|
40
47
|
|
|
41
|
-
owner = path_head_owner(segments.first, scope)
|
|
48
|
+
owner = path_head_owner(segments.first, scope, caller_derived)
|
|
42
49
|
last = segments.size - 1
|
|
43
50
|
segments.each_with_index do |segment, index|
|
|
44
51
|
next if index.zero?
|
|
@@ -50,23 +57,29 @@ module Rigor
|
|
|
50
57
|
end
|
|
51
58
|
owner
|
|
52
59
|
end
|
|
60
|
+
private_class_method :walk_constant_path
|
|
53
61
|
|
|
54
62
|
# Step 2.5's rung as a type: the segment-wise name, then that name looked up like any other
|
|
55
63
|
# candidate so the answer comes from the same source-precedence order as every other rung.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
#
|
|
65
|
+
# Issue #1290 — the final segment's candidates are looked up with the reference's `shadows` names
|
|
66
|
+
# ({.constant_type_at}), so an owner's own `B` another file writes stops the walk before the owner's
|
|
67
|
+
# superclass answers with its published `B`.
|
|
68
|
+
def constant_path_type(name, scope, shadows:, caller_derived: true)
|
|
69
|
+
known = ->(candidate) { constant_type_at(candidate, scope, shadows) }
|
|
70
|
+
qualified = walk_constant_path(name, scope, known, caller_derived: caller_derived)
|
|
71
|
+
qualified && constant_type_at(qualified, scope, shadows)
|
|
59
72
|
end
|
|
60
73
|
private_class_method :constant_path_type
|
|
61
74
|
|
|
62
75
|
# The namespace a path's FIRST segment names — the bare-name ladder, restricted to answers that can
|
|
63
76
|
# OWN the segments that follow. A value constant is not one of them, so it declines here rather
|
|
64
77
|
# than producing a namespace prefix no source knows.
|
|
65
|
-
def path_head_owner(head, scope)
|
|
78
|
+
def path_head_owner(head, scope, caller_derived)
|
|
66
79
|
hit = first_namespace_hit(lexical_nesting_chain(scope), head, scope)
|
|
67
80
|
return hit if hit
|
|
68
81
|
|
|
69
|
-
prefix = enclosing_class_path(scope)
|
|
82
|
+
prefix = caller_derived ? enclosing_class_path(scope) : nil
|
|
70
83
|
unless prefix.nil? || prefix.empty?
|
|
71
84
|
hit = first_namespace_hit(bounded_ancestor_scopes(prefix, scope), head, scope)
|
|
72
85
|
return hit if hit
|
data/lib/rigor/reflection.rb
CHANGED
|
@@ -45,9 +45,10 @@ module Rigor
|
|
|
45
45
|
# out of scope for the v0.0.7 first pass; v0.1.0's plugin API added it as a separate
|
|
46
46
|
# concern.
|
|
47
47
|
module Reflection
|
|
48
|
-
# #
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
# Issue #1290 — what {.constant_type_at} answers for a candidate the project writes but no source types, on
|
|
49
|
+
# a ladder that checks the census. Never a type: {.resolve_constant_type} replaces it before it returns.
|
|
50
|
+
WRITTEN_CANDIDATE = Object.new.freeze
|
|
51
|
+
private_constant :WRITTEN_CANDIDATE
|
|
51
52
|
|
|
52
53
|
module_function
|
|
53
54
|
|
|
@@ -140,10 +141,16 @@ module Rigor
|
|
|
140
141
|
# answer to `::Foo`, and falling back to it was the bug (`::Rails` inside `module MyApp` typed
|
|
141
142
|
# as `MyApp::Rails`).
|
|
142
143
|
#
|
|
144
|
+
# `caller_derived: false` drops what {.toplevel_first_constant_type} takes from the caller — its
|
|
145
|
+
# namespaces and ancestors, for a bare name and for a path's head — for a reader whose miss is a
|
|
146
|
+
# meaningful answer rather than a read Ruby raises on. A constant compound write is one: an unset
|
|
147
|
+
# top-level `REGISTRY ||= {}` is the memoization idiom, and the caller's `Plugin::REGISTRY` is a binding
|
|
148
|
+
# Ruby never consults there. A path's later segments still walk their owner's ancestors.
|
|
149
|
+
#
|
|
143
150
|
# This is the shared owner of the lexical-constant resolution: `Inference::ExpressionTyper`
|
|
144
151
|
# reads it to type a constant read, and `Inference::Narrowing` reads it to recognise a
|
|
145
152
|
# value-pinned `Constant[Regexp]` match-predicate operand.
|
|
146
|
-
def resolve_constant_type(name, scope: Scope.empty, rooted: false)
|
|
153
|
+
def resolve_constant_type(name, scope: Scope.empty, rooted: false, caller_derived: true)
|
|
147
154
|
# Issue #644 — the ADR-46 name edge, recorded ONCE PER REFERENCE and BEFORE the ladder, so it does not
|
|
148
155
|
# depend on which resolver answers. A reference that resolves through RBS (`Math::PI`) or through the
|
|
149
156
|
# class registry is exactly as dependent on the project's constant write set as one that resolves
|
|
@@ -153,10 +160,25 @@ module Rigor
|
|
|
153
160
|
# go stale. Over-recording is ADR-46's safe direction.
|
|
154
161
|
record_constant_reference(name) if Analysis::DependencyRecorder.active?
|
|
155
162
|
return constant_type_at(name, scope) if rooted
|
|
163
|
+
|
|
164
|
+
# Issue #1290 — a candidate the project writes but no source types is where Ruby's lookup stops, so it
|
|
165
|
+
# REPLACES what a lower rung answers, and nothing else. The first pass hands the census's shadowing
|
|
166
|
+
# names to the rungs ({.constant_type_at}); a stop is `Dynamic[top]` only when the plain ladder, run
|
|
167
|
+
# again, answers below it. When nothing below answers, the reference stays unresolved exactly as before,
|
|
168
|
+
# so the nil its caller turns into the fallback, the missing-constant edge and the gem-origin label stay.
|
|
169
|
+
hit = lexical_constant_type(name, scope, caller_derived, scope.shadowing_constant_names(name.to_s))
|
|
170
|
+
return hit unless hit.equal?(WRITTEN_CANDIDATE)
|
|
171
|
+
|
|
172
|
+
Type::Combinator.untyped if lexical_constant_type(name, scope, caller_derived, nil)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# {.resolve_constant_type}'s unrooted ladder. `shadows` is the census's shadowing names for the
|
|
176
|
+
# reference, which the rungs above the last check their candidates against, or nil for the plain ladder.
|
|
177
|
+
def lexical_constant_type(name, scope, caller_derived, shadows)
|
|
156
178
|
# Issue #716 — a body whose `Module.nesting` was RECORDED EMPTY is definitively top level, and Ruby
|
|
157
179
|
# resolves its constants there no matter which namespace calls it. Steps 1 and 2 both derive from the
|
|
158
180
|
# caller and are both wrong for it, so it takes its own ladder.
|
|
159
|
-
return toplevel_first_constant_type(name, scope) if recorded_toplevel_nesting?(scope)
|
|
181
|
+
return toplevel_first_constant_type(name, scope, caller_derived, shadows) if recorded_toplevel_nesting?(scope)
|
|
160
182
|
|
|
161
183
|
# Step 1 — `Module.nesting`, innermost first. Each entry contributes only its OWN constants.
|
|
162
184
|
#
|
|
@@ -174,19 +196,20 @@ module Rigor
|
|
|
174
196
|
# beating the top-level whole-string answer is the point: `A::B` inside `P::Guard` with
|
|
175
197
|
# `P::A < P::Base` is `P::Base::B`, and step 3's `A::B` is a real but different class.
|
|
176
198
|
#
|
|
177
|
-
# Step 3 — the bare name (top level).
|
|
178
|
-
first_constant_hit(lexical_nesting_chain(scope), name, scope) ||
|
|
179
|
-
ancestor_constant_type(name, scope, enclosing_class_path(scope)) ||
|
|
180
|
-
constant_path_type(name, scope) ||
|
|
199
|
+
# Step 3 — the bare name (top level). It takes no `shadows`: nothing sits below it for a stop to replace.
|
|
200
|
+
first_constant_hit(lexical_nesting_chain(scope), name, scope, shadows) ||
|
|
201
|
+
ancestor_constant_type(name, scope, enclosing_class_path(scope), shadows) ||
|
|
202
|
+
constant_path_type(name, scope, shadows: shadows) ||
|
|
181
203
|
constant_type_at(name, scope)
|
|
182
204
|
end
|
|
205
|
+
private_class_method :lexical_constant_type
|
|
183
206
|
|
|
184
207
|
# The first candidate `<entry>::<name>` any source knows, walking `entries` in order, or nil. The one
|
|
185
208
|
# place a qualifying prefix is joined to a name, so every rung of the ladder consults its candidates
|
|
186
209
|
# identically.
|
|
187
|
-
def first_constant_hit(entries, name, scope)
|
|
210
|
+
def first_constant_hit(entries, name, scope, shadows)
|
|
188
211
|
entries.each do |entry|
|
|
189
|
-
hit = constant_type_at("#{entry}::#{name}", scope)
|
|
212
|
+
hit = constant_type_at("#{entry}::#{name}", scope, shadows)
|
|
190
213
|
return hit if hit
|
|
191
214
|
end
|
|
192
215
|
nil
|
|
@@ -194,10 +217,10 @@ module Rigor
|
|
|
194
217
|
private_class_method :first_constant_hit
|
|
195
218
|
|
|
196
219
|
# Step 2's rung on its own: nil when there is no enclosing class path to take ancestors of.
|
|
197
|
-
def ancestor_constant_type(name, scope, prefix)
|
|
220
|
+
def ancestor_constant_type(name, scope, prefix, shadows)
|
|
198
221
|
return nil if prefix.nil? || prefix.empty?
|
|
199
222
|
|
|
200
|
-
first_constant_hit(ancestor_constant_scopes(prefix, scope), name, scope)
|
|
223
|
+
first_constant_hit(ancestor_constant_scopes(prefix, scope), name, scope, shadows)
|
|
201
224
|
end
|
|
202
225
|
private_class_method :ancestor_constant_type
|
|
203
226
|
|
|
@@ -222,21 +245,27 @@ module Rigor
|
|
|
222
245
|
#
|
|
223
246
|
# Issue #656's rung goes LAST here rather than second: for a top-level body Ruby's own first answer
|
|
224
247
|
# for `A::B` is the top level, and a whole-string hit there already means "`A` owns `B`". The
|
|
225
|
-
# segment-wise walk only has something to add once that misses.
|
|
226
|
-
|
|
227
|
-
|
|
248
|
+
# segment-wise walk only has something to add once that misses. Its head consults the caller's
|
|
249
|
+
# ancestors, so under `caller_derived: false` it resolves the head at the top level alone.
|
|
250
|
+
#
|
|
251
|
+
# Issue #1290 — the top level is this ladder's FIRST rung, so a top-level name the project writes stops it
|
|
252
|
+
# ({.constant_type_at}) before a caller's `Plug::TOP` can answer a read Ruby gives to the top-level `TOP`.
|
|
253
|
+
def toplevel_first_constant_type(name, scope, caller_derived, shadows)
|
|
254
|
+
constant_type_at(name, scope, shadows) ||
|
|
255
|
+
(caller_derived && caller_derived_constant_type(name, scope, shadows)) ||
|
|
256
|
+
constant_path_type(name, scope, caller_derived: caller_derived, shadows: shadows)
|
|
228
257
|
end
|
|
229
258
|
private_class_method :toplevel_first_constant_type
|
|
230
259
|
|
|
231
260
|
# The peel and its ancestors — {.resolve_constant_type}'s steps 1 and 2 as they answer for a scope with
|
|
232
261
|
# no recorded chain, factored out so the top-level ladder above can consult them BELOW the top level
|
|
233
262
|
# instead of above it.
|
|
234
|
-
def caller_derived_constant_type(name, scope)
|
|
263
|
+
def caller_derived_constant_type(name, scope, shadows)
|
|
235
264
|
prefix = enclosing_class_path(scope)
|
|
236
265
|
return nil if prefix.nil? || prefix.empty?
|
|
237
266
|
|
|
238
|
-
first_constant_hit(peeled_nesting(prefix), name, scope) ||
|
|
239
|
-
ancestor_constant_type(name, scope, prefix)
|
|
267
|
+
first_constant_hit(peeled_nesting(prefix), name, scope, shadows) ||
|
|
268
|
+
ancestor_constant_type(name, scope, prefix, shadows)
|
|
240
269
|
end
|
|
241
270
|
private_class_method :caller_derived_constant_type
|
|
242
271
|
|
|
@@ -244,7 +273,25 @@ module Rigor
|
|
|
244
273
|
# `Singleton[C]`), source-discovered classes, in-source value constants, then RBS-side
|
|
245
274
|
# constants. In-source values win over RBS constant decls because the user's source is
|
|
246
275
|
# authoritative for its own constants. Returns nil when no source knows `candidate`.
|
|
247
|
-
|
|
276
|
+
#
|
|
277
|
+
# Issue #1290 — `shadows`, passed on every rung that has a rung below it, is the last source: a candidate
|
|
278
|
+
# no other source types answers {WRITTEN_CANDIDATE} when `shadows` holds it, and {.resolve_constant_type}
|
|
279
|
+
# settles that stop. `shadows` is `Scope#shadowing_constant_names` for the reference, whose last segment
|
|
280
|
+
# every candidate shares. Ruby's lookup stops at a constant that exists, whatever its value, and the
|
|
281
|
+
# cross-file table publishes only a frozen scalar one file writes: another file's `App::LIMIT = { n: 1 }`
|
|
282
|
+
# was missed by every source, and the ladder moved on to a published top-level `LIMIT = 5`, typing `LIMIT`
|
|
283
|
+
# inside `module App` as `5` and reporting `call.undefined-method` on correct code. The value is one the
|
|
284
|
+
# analyzer does not carry, so the stop is gradual.
|
|
285
|
+
#
|
|
286
|
+
# Only an EXACT census name stops a candidate. The census keeps a path write as written
|
|
287
|
+
# ([#690](https://github.com/rigortype/rigor/issues/690)), so a suffix match would reach the as-written
|
|
288
|
+
# `Foo::BAR` from `M::Foo::BAR`, but would also stop `Admin::UsersController::X` on a top-level
|
|
289
|
+
# `UsersController::X`, losing the answer an ordinary Rails layout gets from the namespaced controller's
|
|
290
|
+
# superclass. A name written only through `||=` does not shadow, however many files memoize it: `X ||= v`
|
|
291
|
+
# resolves `X` through this same lookup, so wherever a lower rung answers, the memo finds that constant and
|
|
292
|
+
# never creates the name. The answer moves with the project's write set for the last segment, which the
|
|
293
|
+
# `constant:` edge {.resolve_constant_type} records once per reference already covers.
|
|
294
|
+
def constant_type_at(candidate, scope, shadows = nil)
|
|
248
295
|
env = scope.environment
|
|
249
296
|
|
|
250
297
|
singleton = env.singleton_for_name(candidate)
|
|
@@ -264,7 +311,7 @@ module Rigor
|
|
|
264
311
|
return in_source_value
|
|
265
312
|
end
|
|
266
313
|
|
|
267
|
-
env.constant_for_name(candidate)
|
|
314
|
+
env.constant_for_name(candidate) || (WRITTEN_CANDIDATE if shadows&.include?(candidate))
|
|
268
315
|
end
|
|
269
316
|
private_class_method :constant_type_at
|
|
270
317
|
|
|
@@ -280,87 +327,6 @@ module Rigor
|
|
|
280
327
|
end
|
|
281
328
|
private_class_method :record_constant_reference
|
|
282
329
|
|
|
283
|
-
# #354 — the project classes and modules whose own constants `class_name` inherits, in Ruby's
|
|
284
|
-
# ancestor order: included / prepended modules before the superclass (Ruby places mixins nearer),
|
|
285
|
-
# transitively, breadth-first. `class_name` itself is excluded — step 1 already covered it.
|
|
286
|
-
#
|
|
287
|
-
# Only PROJECT ancestors appear. `Scope#superclass_of` / `#includes_of` carry as-written names
|
|
288
|
-
# from the discovery pre-pass, and an as-written name that resolves to no discovered class or
|
|
289
|
-
# module is dropped — so a `class Foo < ActiveRecord::Base` contributes nothing and a constant
|
|
290
|
-
# owned by an RBS-known ancestor still resolves only if the bare name reaches it at step 3. That
|
|
291
|
-
# gap is deliberate for this slice: widening to the RBS ancestor graph is a separate question
|
|
292
|
-
# with its own FP surface.
|
|
293
|
-
#
|
|
294
|
-
# Memoised per run because step 2 runs on every constant reference whose lexical candidates all
|
|
295
|
-
# miss — which is the common case for a core-class reference (`String` inside `class Foo`). The
|
|
296
|
-
# bucket keys on the identity of the runner-seeded run-generation token (ADR-84 WD2), falling
|
|
297
|
-
# back to the per-file discovery table for runner-less scopes, so a re-run in one process (LSP,
|
|
298
|
-
# ADR-62 warm loop) cannot hit stale entries.
|
|
299
|
-
def ancestor_constant_scopes(class_name, scope)
|
|
300
|
-
# ADR-46: `superclass_of` / `includes_of` record a cross-file class dependency per consumer
|
|
301
|
-
# file, and the memo is run-scoped rather than file-scoped — a hit would skip the recording and
|
|
302
|
-
# under-record the edge for every later file. Recording runs are rare (incremental only), so
|
|
303
|
-
# they simply bypass the memo rather than complicate its key.
|
|
304
|
-
return compute_ancestor_constant_scopes(class_name, scope) if Analysis::DependencyRecorder.active?
|
|
305
|
-
|
|
306
|
-
generation = scope.run_generation || scope.discovered_superclasses
|
|
307
|
-
slot = Thread.current[ANCESTOR_SCOPES_KEY]
|
|
308
|
-
unless slot && slot[0].equal?(generation)
|
|
309
|
-
slot = [generation, {}]
|
|
310
|
-
Thread.current[ANCESTOR_SCOPES_KEY] = slot
|
|
311
|
-
end
|
|
312
|
-
bucket = slot[1]
|
|
313
|
-
bucket.fetch(class_name) { bucket[class_name] = compute_ancestor_constant_scopes(class_name, scope) }
|
|
314
|
-
end
|
|
315
|
-
private_class_method :ancestor_constant_scopes
|
|
316
|
-
|
|
317
|
-
def compute_ancestor_constant_scopes(class_name, scope)
|
|
318
|
-
queue = [class_name]
|
|
319
|
-
seen = { class_name => true }
|
|
320
|
-
out = []
|
|
321
|
-
until queue.empty?
|
|
322
|
-
current = queue.shift
|
|
323
|
-
# Mixins first, then the superclass — Ruby's ancestor order.
|
|
324
|
-
scope.includes_of(current).each do |raw|
|
|
325
|
-
resolved = resolve_ancestor_name(current, raw, scope)
|
|
326
|
-
next if resolved.nil? || seen[resolved]
|
|
327
|
-
|
|
328
|
-
seen[resolved] = true
|
|
329
|
-
out << resolved
|
|
330
|
-
queue << resolved
|
|
331
|
-
end
|
|
332
|
-
raw_super = scope.superclass_of(current)
|
|
333
|
-
next if raw_super.nil?
|
|
334
|
-
|
|
335
|
-
resolved_super = resolve_ancestor_name(current, raw_super, scope)
|
|
336
|
-
next if resolved_super.nil? || seen[resolved_super]
|
|
337
|
-
|
|
338
|
-
seen[resolved_super] = true
|
|
339
|
-
out << resolved_super
|
|
340
|
-
queue << resolved_super
|
|
341
|
-
end
|
|
342
|
-
out.freeze
|
|
343
|
-
end
|
|
344
|
-
private_class_method :compute_ancestor_constant_scopes
|
|
345
|
-
|
|
346
|
-
# Resolves an ancestor name AS WRITTEN (`"Base"`, or a qualified `"A::B"`) against the nesting in
|
|
347
|
-
# force where the subclass's header is written — `Scope#ancestor_name_candidates`, the single
|
|
348
|
-
# owner of that order, which `Scope#enqueue_ancestors` reads for method lookup and the
|
|
349
|
-
# override-visibility rule reads for its own walk. Returns nil when no candidate names a
|
|
350
|
-
# discovered project class or module.
|
|
351
|
-
def resolve_ancestor_name(subclass_qualified, raw, scope)
|
|
352
|
-
scope.ancestor_name_candidates(subclass_qualified, raw)
|
|
353
|
-
.find { |candidate| known_project_namespace?(candidate, scope) }
|
|
354
|
-
end
|
|
355
|
-
private_class_method :resolve_ancestor_name
|
|
356
|
-
|
|
357
|
-
def known_project_namespace?(name, scope)
|
|
358
|
-
scope.discovered_superclasses.key?(name) ||
|
|
359
|
-
scope.discovered_includes.key?(name) ||
|
|
360
|
-
scope.discovered_classes.key?(name)
|
|
361
|
-
end
|
|
362
|
-
private_class_method :known_project_namespace?
|
|
363
|
-
|
|
364
330
|
# Pulls the enclosing qualified class name out of `scope.self_type` when one is set.
|
|
365
331
|
# `Nominal[T]` and `Singleton[T]` both expose `class_name`. Returns nil at the top level.
|
|
366
332
|
def enclosing_class_path(scope)
|
|
@@ -447,7 +413,7 @@ module Rigor
|
|
|
447
413
|
nil
|
|
448
414
|
end
|
|
449
415
|
|
|
450
|
-
#
|
|
416
|
+
# The singleton-side twin of {#instance_definition}, nil under the same conditions.
|
|
451
417
|
def singleton_definition(class_name, scope: nil, environment: nil)
|
|
452
418
|
loader = rbs_loader_for(scope, environment)
|
|
453
419
|
return nil if loader.nil?
|
|
@@ -510,6 +476,7 @@ module Rigor
|
|
|
510
476
|
end
|
|
511
477
|
end
|
|
512
478
|
|
|
513
|
-
# Loaded last: the segment-wise path walk
|
|
514
|
-
# lookups, so the facade has to exist first.
|
|
479
|
+
# Loaded last: the ancestor walk and the segment-wise path walk reopen the module above and read its
|
|
480
|
+
# private candidate lookups, so the facade has to exist first.
|
|
481
|
+
require_relative "reflection/constant_ancestors"
|
|
515
482
|
require_relative "reflection/constant_path"
|
|
@@ -15,6 +15,7 @@ module Rigor
|
|
|
15
15
|
:class_ivars,
|
|
16
16
|
:class_cvars,
|
|
17
17
|
:program_globals,
|
|
18
|
+
:program_global_seeds,
|
|
18
19
|
:discovered_classes,
|
|
19
20
|
:in_source_constants,
|
|
20
21
|
:discovered_methods,
|
|
@@ -24,12 +25,19 @@ module Rigor
|
|
|
24
25
|
:discovered_def_sources,
|
|
25
26
|
:discovered_singleton_def_sources,
|
|
26
27
|
:discovered_method_visibilities,
|
|
28
|
+
:discovered_parameter_envelopes,
|
|
27
29
|
:discovered_superclasses,
|
|
30
|
+
:discovered_deferred_ranges,
|
|
31
|
+
:discovered_refinements,
|
|
32
|
+
:discovered_global_write_census,
|
|
28
33
|
:discovered_header_nestings,
|
|
29
34
|
:discovered_includes,
|
|
35
|
+
:discovered_prepends,
|
|
30
36
|
:discovered_extends,
|
|
31
37
|
:discovered_class_sources,
|
|
32
38
|
:constant_sources,
|
|
39
|
+
:constant_writers,
|
|
40
|
+
:constant_shadowers,
|
|
33
41
|
:published_constant_names,
|
|
34
42
|
:local_constant_names,
|
|
35
43
|
:published_constant_alias_names,
|
|
@@ -37,7 +45,11 @@ module Rigor
|
|
|
37
45
|
:data_member_layouts,
|
|
38
46
|
:struct_member_layouts,
|
|
39
47
|
:param_inferred_types,
|
|
40
|
-
:run_generation
|
|
48
|
+
:run_generation,
|
|
49
|
+
:patched_line_readers,
|
|
50
|
+
:clears_last_status,
|
|
51
|
+
:defines_case_equality,
|
|
52
|
+
:implicit_self_evidence
|
|
41
53
|
)
|
|
42
54
|
|
|
43
55
|
class DiscoveryIndex
|
|
@@ -60,6 +72,52 @@ module Rigor
|
|
|
60
72
|
# as written, and no ancestor name can be nil.
|
|
61
73
|
UNKEYED_HEADER_NESTING = nil
|
|
62
74
|
|
|
75
|
+
# Issue #986 — a bucket entry is normally ONE chain (`["Wrap"]`, innermost first). When the
|
|
76
|
+
# compact-header rename pass lands two declarations of one class on a single key and both wrote the
|
|
77
|
+
# same raw ancestor name in DIFFERENT crefs, the entry is instead the LIST of those chains —
|
|
78
|
+
# `[["Wrap"], []]` — because no union of them is Ruby's answer for either site. `Scope` resolves each
|
|
79
|
+
# alternative and declines outright when they name two different project classes: which one wins
|
|
80
|
+
# depends on the runtime load order of the two `include`s, which static analysis cannot know, so
|
|
81
|
+
# picking either is unsound. A chain's entries are Strings and an alternatives list's are Arrays, so
|
|
82
|
+
# the two shapes cannot be confused.
|
|
83
|
+
def self.ambiguous_header_nesting?(entries)
|
|
84
|
+
entries.first.is_a?(Array)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Issue #992 — the two class-wide keys a `discovered_parameter_envelopes` bucket can carry beside its
|
|
88
|
+
# `[kind, method_name]` entries. Their PRESENCE is the fact; the value is always
|
|
89
|
+
# `Source::ParameterEnvelope::OPAQUE`, so a bucket folds under the one join every other entry does.
|
|
90
|
+
# Symbols, because every per-method key is an Array and neither can collide with the other.
|
|
91
|
+
#
|
|
92
|
+
# - {ENVELOPE_MODULE_MARK}: the name is declared with `module` (or `Const = Module.new do … end`) somewhere
|
|
93
|
+
# in the project. An instance of it is an instance of an unknown includer.
|
|
94
|
+
# - {ENVELOPE_DYNAMIC_MARK}: the class body rewrites its method table in a way no literal argument names
|
|
95
|
+
# (`class_eval`, a computed `define_method`, `send`, a non-constant mixin), or a constant-receiver form
|
|
96
|
+
# of those names it from outside.
|
|
97
|
+
# - {ENVELOPE_OBJECT_EXTENDED_MARK}: some method body passes the module to `extend`, so an object of any
|
|
98
|
+
# class may carry its instance methods ahead of that class's own.
|
|
99
|
+
# - {ENVELOPE_REFINED_MARK}: a `refine` block refines the class, which redefines its methods wherever a `using`
|
|
100
|
+
# is in effect. Issue #1367 splits it from {ENVELOPE_DYNAMIC_MARK}, which it used to share: an implicit
|
|
101
|
+
# conversion (`to_str`, `to_int`) ignores a refinement, so the `global.*` rules must tell the two apart.
|
|
102
|
+
# Every reader that asks whether a surface is rewritten reads both ({.rewritten_surface?}).
|
|
103
|
+
ENVELOPE_MODULE_MARK = :"<module>"
|
|
104
|
+
ENVELOPE_DYNAMIC_MARK = :"<dynamic>"
|
|
105
|
+
ENVELOPE_OBJECT_EXTENDED_MARK = :"<object-extended>"
|
|
106
|
+
ENVELOPE_REFINED_MARK = :"<refined>"
|
|
107
|
+
|
|
108
|
+
# Whether an envelope bucket says the project rewrites the class's method table beyond its literal names: a
|
|
109
|
+
# dynamic rewrite, or a refinement.
|
|
110
|
+
def self.rewritten_surface?(bucket)
|
|
111
|
+
bucket.key?(ENVELOPE_DYNAMIC_MARK) || bucket.key?(ENVELOPE_REFINED_MARK)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Issue #992 — the class key a WHOLE-PROJECT discovery pass adds to `discovered_parameter_envelopes`
|
|
115
|
+
# (`ScopeIndexer#finalize_def_index`), with an empty bucket. A single file's walk alone — `run_source`,
|
|
116
|
+
# the LSP `prebuilt:` scope — sees one `def` and cannot see the reopening, subclass or `class_eval` in
|
|
117
|
+
# another file that would make it opaque, so `call.wrong-arity` reads an envelope only when this key is
|
|
118
|
+
# present. Not a constant character, so no class can be named it.
|
|
119
|
+
ENVELOPE_PROJECT_WIDE = "<project-wide>"
|
|
120
|
+
|
|
63
121
|
# The shared all-empty index `Scope.empty` (and every scope that never sees a seeding pass) points at — one
|
|
64
122
|
# allocation per process.
|
|
65
123
|
EMPTY = new(
|
|
@@ -67,6 +125,12 @@ module Rigor
|
|
|
67
125
|
class_ivars: EMPTY_TABLE,
|
|
68
126
|
class_cvars: EMPTY_TABLE,
|
|
69
127
|
program_globals: EMPTY_TABLE,
|
|
128
|
+
# Issue #1362 — for each global in `program_globals` that Ruby's own signatures declare (`$VERBOSE: bool?`,
|
|
129
|
+
# `$stdout: IO`), its declared type joined with its writes in the file: what every method body and the top
|
|
130
|
+
# level start from, under the ADR-58 `:global` mark. Any other global starts from its `program_globals` entry,
|
|
131
|
+
# which keeps the writes alone for the consumers that compare against them (the mark's gates, and `$;` for
|
|
132
|
+
# `split`). Filled by `Inference::ScopeIndexer.index` from the file's own tree only.
|
|
133
|
+
program_global_seeds: EMPTY_TABLE,
|
|
70
134
|
discovered_classes: EMPTY_TABLE,
|
|
71
135
|
in_source_constants: EMPTY_TABLE,
|
|
72
136
|
discovered_methods: EMPTY_TABLE,
|
|
@@ -87,7 +151,37 @@ module Rigor
|
|
|
87
151
|
discovered_def_sources: EMPTY_TABLE,
|
|
88
152
|
discovered_singleton_def_sources: EMPTY_TABLE,
|
|
89
153
|
discovered_method_visibilities: EMPTY_TABLE,
|
|
154
|
+
# Issue #992 — `{qualified class name => {[kind, method_name] => Source::ParameterEnvelope}}`, the
|
|
155
|
+
# parameter envelope of every method the declaration walk records, plus {ENVELOPE_MODULE_MARK} /
|
|
156
|
+
# {ENVELOPE_DYNAMIC_MARK}. Written beside `discovered_methods` by one recorder, so an `alias` / `attr_*`
|
|
157
|
+
# / `define_method` name is always present here as `OPAQUE`, and folded across files and reopenings
|
|
158
|
+
# with `Source::ParameterEnvelope.merge`: one value per name, a real envelope only while every
|
|
159
|
+
# contribution agrees. Read by `call.wrong-arity` for a method no signature declares.
|
|
160
|
+
discovered_parameter_envelopes: EMPTY_TABLE,
|
|
90
161
|
discovered_superclasses: EMPTY_TABLE,
|
|
162
|
+
# Issue #1097 — `{file path => [[start_offset, end_offset, name, kind, owner], ...]}`, every
|
|
163
|
+
# `def` / block / lambda body range in the file. `Scope#*_def_shadows_call?` reads it to tell
|
|
164
|
+
# an eager class-body call (orderable by offset against a same-name, same-owner def entry)
|
|
165
|
+
# from a deferred one (contained in any range — it runs at invocation time, when every
|
|
166
|
+
# class-body def exists). Def entries carry the method name, the `:instance` / `:singleton` /
|
|
167
|
+
# `:both` (module_function) kind, and the qualified owner; block / lambda rows and defs nested
|
|
168
|
+
# inside another deferred range carry nils and answer only the containment half. Plain data,
|
|
169
|
+
# so the ADR-85 seed bundle round-trips it unchanged.
|
|
170
|
+
discovered_deferred_ranges: EMPTY_TABLE,
|
|
171
|
+
# Issue #1120 — `{refined class name => {method name => [refining module names]}}`, the instance
|
|
172
|
+
# methods a `refine X do … end` block defines. They are not X's methods everywhere: Ruby activates them
|
|
173
|
+
# only lexically after a `using` of the refining module, so `call.undefined-method` reads this table
|
|
174
|
+
# together with the call site's file ({Analysis::CheckRules::LexicalMethodSites}) and never through
|
|
175
|
+
# `discovered_methods`. A refinement a `Module.new { … }` block defines has no nameable module; it is
|
|
176
|
+
# keyed by the name the walk gives the block's owner. Plain data, so the ADR-85 seed bundle
|
|
177
|
+
# round-trips it unchanged.
|
|
178
|
+
discovered_refinements: EMPTY_TABLE,
|
|
179
|
+
# Issue #1367 — the project's `Inference::GlobalWriteCensus`: the globals any `alias` names, the
|
|
180
|
+
# `write` / `to_str` / `to_int` / hatch names any file defines (inside a refinement or not), and the
|
|
181
|
+
# modules a top-level mixin names, over every file and inside method bodies too. The `global.*` write rules
|
|
182
|
+
# read it. The project pre-pass seeds the whole project's census and `Inference::ScopeIndexer.index` adds the
|
|
183
|
+
# analysed file's own. Plain data, so the ADR-85 seed bundle round-trips it unchanged.
|
|
184
|
+
discovered_global_write_census: EMPTY_NAME_SET,
|
|
91
185
|
# Issue #682 — `{qualified class name => Module.nesting where its declaration HEADER is written}`,
|
|
92
186
|
# innermost first and EXCLUDING the declaration's own entry. Read by `Scope#ancestor_name_candidates`,
|
|
93
187
|
# which resolves a superclass / include name in that cref instead of peeling the subclass's qualified
|
|
@@ -102,6 +196,14 @@ module Rigor
|
|
|
102
196
|
# a name no site recorded under its own key.
|
|
103
197
|
discovered_header_nestings: EMPTY_TABLE,
|
|
104
198
|
discovered_includes: EMPTY_TABLE,
|
|
199
|
+
# Issue #1123 — `{qualified class or module name => [module names it `prepend`s, as written]}`,
|
|
200
|
+
# stored in instance-ancestor SEARCH order (nearest prepend first), the `discovered_extends`
|
|
201
|
+
# convention. The instance-side twin `discovered_includes` holds the same names — prepends
|
|
202
|
+
# included — in that order too since #1173: prepends ahead of includes, each nearest-first. The
|
|
203
|
+
# kind still cannot be told apart there, so `Scope#user_def_through_ancestors` reads the wedge
|
|
204
|
+
# from here. An absent entry means "prepends nothing", which is the un-preprended behaviour
|
|
205
|
+
# every scope had before it existed.
|
|
206
|
+
discovered_prepends: EMPTY_TABLE,
|
|
105
207
|
# Issue #898 — the singleton-side twin of `discovered_includes`: `{qualified class or module name =>
|
|
106
208
|
# [module names it `extend`s, as written]}`, built by the same `ScopeIndexer` walk that #526 already
|
|
107
209
|
# ran to fold an extended module's instance defs onto the extending class's singleton. #526 consumed
|
|
@@ -114,6 +216,16 @@ module Rigor
|
|
|
114
216
|
# dependency recording, so the runner seeds it only on a recording run; every other run leaves it
|
|
115
217
|
# empty and the edge costs one nil check.
|
|
116
218
|
constant_sources: EMPTY_TABLE,
|
|
219
|
+
# Issue #617 — the censused names that bind (any write other than a memo `||=`, or memos of the
|
|
220
|
+
# segment in two files), grouped by LAST SEGMENT, wildcard keys (`*::LIMIT`) under their segment.
|
|
221
|
+
# `Scope#bound_constant_names` reads it for a constant compound write whose plain read resolves to
|
|
222
|
+
# nothing: such a name is bound, just not to a value the analyzer carries. Seeded on every run, because
|
|
223
|
+
# the question is a typing one rather than a recording one.
|
|
224
|
+
constant_writers: EMPTY_TABLE,
|
|
225
|
+
# Issue #1290 — the censused names some write other than a memo `||=` assigns, grouped by LAST SEGMENT.
|
|
226
|
+
# `Scope#shadowing_constant_names` reads it for the lexical ladder: a candidate no source types but this
|
|
227
|
+
# table holds is where Ruby's lookup stops. Seeded on every run beside `constant_writers`.
|
|
228
|
+
constant_shadowers: EMPTY_TABLE,
|
|
117
229
|
# Issue #644 — the two halves of `Scope#published_constant?`, the question
|
|
118
230
|
# {Analysis::CheckRules::PublishedConstantGuard} asks. `published_constant_names` is the LAST
|
|
119
231
|
# SEGMENTS of the project-wide published table (run-wide, seeded by the runner);
|
|
@@ -147,7 +259,28 @@ module Rigor
|
|
|
147
259
|
# cross a run boundary (LSP / ADR-62 warm-loop re-runs land in a fresh bucket). Nil on scopes that
|
|
148
260
|
# never see the runner seed (single-file probes, `run_source` before the seed applies): the memo
|
|
149
261
|
# falls back to today's per-file `discovered_def_nodes` identity for those.
|
|
150
|
-
run_generation: nil
|
|
262
|
+
run_generation: nil,
|
|
263
|
+
# Issue #1359 — the `gets` / `readline` names this file patches in through the `define_method` family
|
|
264
|
+
# (`$stdin.define_singleton_method(:gets) { … }`, `IO.define_method(:gets)`, `alias_method :gets, :x`, or a
|
|
265
|
+
# computed name, which may be either), which `Inference::LastLine.reads_line?` declines on, as it does on a
|
|
266
|
+
# name `BlockCallTiming.project_defines_anywhere?` finds. Filled by `Inference::ScopeIndexer.index` from the
|
|
267
|
+
# file's own tree only.
|
|
268
|
+
patched_line_readers: EMPTY_NAME_SET,
|
|
269
|
+
# Issue #1360 — true when this file holds a call that may set `$?` to nil (`Inference::LastStatus.clears?`: a
|
|
270
|
+
# `wait`-family call with a flags argument, or `waitall`), which may run between any subprocess and a read of
|
|
271
|
+
# `$?`, so `Inference::LastStatus.after` binds it nowhere in the file. Filled by `Inference::ScopeIndexer.index`
|
|
272
|
+
# from the file's own tree only.
|
|
273
|
+
clears_last_status: false,
|
|
274
|
+
# Issue #1360 — true when this file holds a `define_method` or `define_singleton_method` call whose literal
|
|
275
|
+
# name is `===` (`Inference::ErrorInfo.defines_case_equality?`), which may give a class the singleton `===`
|
|
276
|
+
# `rescue` matches with; the class it lands on is not recorded, so no rescued class binds `$!` in the file.
|
|
277
|
+
# Filled by `Inference::ScopeIndexer.index` from the file's own tree only.
|
|
278
|
+
defines_case_equality: false,
|
|
279
|
+
# Issue #1415 — the file's evidence about the `self` its implicit-self `gets` / `readline` readers run with
|
|
280
|
+
# (`Inference::LastLine::SelfEvidence`, read on the first ask), which `Inference::LastLine.reads_line?` reads
|
|
281
|
+
# them by. Filled by `Inference::ScopeIndexer.index` from the file's own tree only; nil, where no file was
|
|
282
|
+
# indexed, declines every such reader.
|
|
283
|
+
implicit_self_evidence: nil
|
|
151
284
|
)
|
|
152
285
|
end
|
|
153
286
|
end
|