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
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module ScopeIndexer
|
|
8
|
+
# ADR-119 WD3 — which of a file's method contributions are `possible`: the classifier the def-contribution
|
|
9
|
+
# producers ({ScopeIndexer#build_methods_and_def_nodes}, {ScopeIndexer#build_discovered_singleton_def_nodes})
|
|
10
|
+
# consult to fill `possible_discovered_methods` and the `contested_*` siblings.
|
|
11
|
+
#
|
|
12
|
+
# A contribution is `certain` when its statement executes whenever the file's top level executes. The certain
|
|
13
|
+
# regions are:
|
|
14
|
+
#
|
|
15
|
+
# - the program's statements, and the bodies of `class`, `module` and `class << self` declarations that are
|
|
16
|
+
# themselves certain;
|
|
17
|
+
# - the block of a meta-new constant write (`K = Class.new do … end`, `Module.new`, `Struct.new`,
|
|
18
|
+
# `Data.define`, {ScopeIndexer#meta_new_block_call}) and of a bare factory call
|
|
19
|
+
# ({AnonymousMetaClass.block_form_receiver}), when the write or the call is certain: Ruby runs the block once,
|
|
20
|
+
# as the new class's body (ADR-119 WD2, "Conditional definers");
|
|
21
|
+
# - a `begin` WITHOUT a `rescue` clause: its main statements and its `ensure` clause;
|
|
22
|
+
# - the receiver and arguments of a certain call, so `private def x` and `memoize def x` stay certain.
|
|
23
|
+
#
|
|
24
|
+
# Everything else is `possible`: the branches of `if` / `unless` / `case` and the modifiers, loop bodies, the
|
|
25
|
+
# right operand of `&&` / `||`, every other block and every lambda, `BEGIN` / `END`, a `class X … end if Y`
|
|
26
|
+
# reopening, and the WHOLE of a `begin` that has a `rescue` clause, its main statements included. That last
|
|
27
|
+
# rule refines WD3's wording: a rescued raise skips every statement after the raising one, so the main body of
|
|
28
|
+
# a rescuing `begin` is not certain either, while a `begin` with no rescue either runs its main statements
|
|
29
|
+
# and `ensure` or propagates the raise out of the file's top level. A `def` inside a method body is never
|
|
30
|
+
# recorded by the walks (they return at a `DefNode`), so the classifier does not descend there either.
|
|
31
|
+
#
|
|
32
|
+
# The answer is an identity Set of the POSSIBLE contribution nodes — the nodes a producer records from — and
|
|
33
|
+
# {EMPTY} when the file has none, so a file written entirely in certain regions allocates nothing. It is
|
|
34
|
+
# separate from {MixinAccumulator}, which answers a different question (whether an edge's POSITION is a fact).
|
|
35
|
+
module Certainty
|
|
36
|
+
# The answer for a file with no possible contribution.
|
|
37
|
+
EMPTY = Set.new.compare_by_identity.freeze
|
|
38
|
+
|
|
39
|
+
# The nodes a def-contribution producer records from: a `def`, an `alias` / `undef`, a call (`attr_*`,
|
|
40
|
+
# `define_method`, `alias_method`, `module_function :x`, a factory's members), and the declarations whose
|
|
41
|
+
# header records members (`class X < Struct.new(:a)`, `X = Struct.new(:a) do … end`).
|
|
42
|
+
CONTRIBUTIONS = Set[
|
|
43
|
+
Prism::DefNode, Prism::AliasMethodNode, Prism::UndefNode, Prism::CallNode, Prism::ClassNode,
|
|
44
|
+
Prism::ConstantWriteNode, Prism::ConstantPathWriteNode, Prism::ConstantOrWriteNode,
|
|
45
|
+
Prism::ConstantPathOrWriteNode
|
|
46
|
+
].freeze
|
|
47
|
+
|
|
48
|
+
CONSTANT_WRITES = Set[
|
|
49
|
+
Prism::ConstantWriteNode, Prism::ConstantPathWriteNode, Prism::ConstantOrWriteNode,
|
|
50
|
+
Prism::ConstantPathOrWriteNode
|
|
51
|
+
].freeze
|
|
52
|
+
|
|
53
|
+
# The nodes whose whole subtree is possible.
|
|
54
|
+
POSSIBLE_ROOTS = Set[
|
|
55
|
+
Prism::BlockNode, Prism::LambdaNode, Prism::PreExecutionNode, Prism::PostExecutionNode,
|
|
56
|
+
Prism::RescueModifierNode
|
|
57
|
+
].freeze
|
|
58
|
+
private_constant :CONSTANT_WRITES, :POSSIBLE_ROOTS
|
|
59
|
+
|
|
60
|
+
module_function
|
|
61
|
+
|
|
62
|
+
# The possible contribution nodes of the tree under `root`, an identity Set ({EMPTY} when none).
|
|
63
|
+
def possible_nodes(root)
|
|
64
|
+
classifier = Classifier.new
|
|
65
|
+
classifier.certain(root)
|
|
66
|
+
classifier.result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Whether `node` was classified possible in `possible` (a {possible_nodes} answer).
|
|
70
|
+
def possible?(possible, node)
|
|
71
|
+
!possible.empty? && possible.include?(node)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# One classification walk. `certain` descends a certain region; `possible` marks every contribution in a
|
|
75
|
+
# subtree, since nothing below a possible node is certain.
|
|
76
|
+
class Classifier
|
|
77
|
+
def initialize
|
|
78
|
+
@possible = nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def result
|
|
82
|
+
@possible ? @possible.freeze : EMPTY
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def certain(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
86
|
+
return unless node.is_a?(Prism::Node)
|
|
87
|
+
return possible(node) if POSSIBLE_ROOTS.include?(node.class)
|
|
88
|
+
|
|
89
|
+
case node
|
|
90
|
+
when Prism::DefNode then return
|
|
91
|
+
when Prism::IfNode then return branch(node.predicate, node.statements, node.subsequent)
|
|
92
|
+
when Prism::UnlessNode then return branch(node.predicate, node.statements, node.else_clause)
|
|
93
|
+
when Prism::CaseNode, Prism::CaseMatchNode then return case_branches(node)
|
|
94
|
+
when Prism::WhileNode, Prism::UntilNode then return branch(node.predicate, node.statements)
|
|
95
|
+
when Prism::ForNode then return branch(node.collection, node.index, node.statements)
|
|
96
|
+
when Prism::AndNode, Prism::OrNode then return branch(node.left, node.right)
|
|
97
|
+
when Prism::BeginNode then return begin_block(node)
|
|
98
|
+
when Prism::CallNode then return call(node)
|
|
99
|
+
end
|
|
100
|
+
return if CONSTANT_WRITES.include?(node.class) && meta_new_write?(node)
|
|
101
|
+
|
|
102
|
+
node.rigor_each_child { |child| certain(child) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Marks every contribution of the subtree.
|
|
106
|
+
def possible(node)
|
|
107
|
+
return unless node.is_a?(Prism::Node)
|
|
108
|
+
|
|
109
|
+
(@possible ||= Set.new.compare_by_identity) << node if CONTRIBUTIONS.include?(node.class)
|
|
110
|
+
return if node.is_a?(Prism::DefNode)
|
|
111
|
+
|
|
112
|
+
node.rigor_each_child { |child| possible(child) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
# The first node runs whenever the construct does; the rest may not.
|
|
118
|
+
def branch(first, *rest)
|
|
119
|
+
certain(first)
|
|
120
|
+
rest.each { |node| possible(node) }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def case_branches(node)
|
|
124
|
+
certain(node.predicate)
|
|
125
|
+
node.conditions.each { |clause| possible(clause) }
|
|
126
|
+
possible(node.else_clause)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Without a rescue the main statements run (or the raise leaves the file) and `ensure` runs; with one, a
|
|
130
|
+
# rescued raise skips every statement after the raising one, so all of it is possible.
|
|
131
|
+
def begin_block(node)
|
|
132
|
+
return possible_children(node) if node.rescue_clause
|
|
133
|
+
|
|
134
|
+
certain(node.statements)
|
|
135
|
+
possible(node.else_clause)
|
|
136
|
+
certain(node.ensure_clause)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def possible_children(node)
|
|
140
|
+
node.rigor_each_child { |child| possible(child) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# The receiver and arguments of a certain call run; its block runs as a class body only for a bare
|
|
144
|
+
# factory call. A safe-navigation call may skip its arguments.
|
|
145
|
+
def call(node)
|
|
146
|
+
certain(node.receiver)
|
|
147
|
+
if node.safe_navigation?
|
|
148
|
+
possible(node.arguments)
|
|
149
|
+
possible(node.block)
|
|
150
|
+
return
|
|
151
|
+
end
|
|
152
|
+
certain(node.arguments)
|
|
153
|
+
block = node.block
|
|
154
|
+
if block.is_a?(Prism::BlockNode) && AnonymousMetaClass.block_form_receiver(node)
|
|
155
|
+
certain(block.body)
|
|
156
|
+
else
|
|
157
|
+
certain(block)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# A meta-new constant write: the factory's receiver and arguments, and its block as the class body.
|
|
162
|
+
def meta_new_write?(node)
|
|
163
|
+
call = ScopeIndexer.meta_new_block_call(node)
|
|
164
|
+
return false unless call
|
|
165
|
+
|
|
166
|
+
certain(call.receiver)
|
|
167
|
+
certain(call.arguments)
|
|
168
|
+
block = call.block
|
|
169
|
+
certain(block.is_a?(Prism::BlockNode) ? block.body : block)
|
|
170
|
+
true
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../declaration_walk/traversal"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module ScopeIndexer
|
|
8
|
+
# The `class_cvars` discovery table (slice 7 phase 6) as a {DeclarationWalk} collector: every
|
|
9
|
+
# `@@x = …` a `def` body writes, typed under the census scope and unioned per class. Ruby cvars are
|
|
10
|
+
# shared by the instance and singleton facets, so every `def` counts, `def self.x` included.
|
|
11
|
+
class ClassCvarsCollector
|
|
12
|
+
include DeclarationWalk::Collector
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@table = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# `@@x` in a `def` resolves through the LEXICAL cref: `Module.nesting` is the same inside a meta-new or
|
|
19
|
+
# eval block, so the context's `prefix` keys the write and its rebound `self` never does —
|
|
20
|
+
# `K = Class.new { def m = @@x }` inside `class C` writes `C::@@x`. The walk goes no further in: the
|
|
21
|
+
# gather reads the body with `def`, `class` and `module` as barriers, and nothing below a `def` reaches
|
|
22
|
+
# this table.
|
|
23
|
+
def on_def(node, context)
|
|
24
|
+
ScopeIndexer.collect_def_cvar_writes(node, context.prefix, context.scope, @table)
|
|
25
|
+
DeclarationWalk::DECLINE
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The table as the discovery index holds it: each class's entry frozen, then the whole.
|
|
29
|
+
def table
|
|
30
|
+
@table.transform_values(&:freeze).freeze
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../declaration_walk/traversal"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module ScopeIndexer
|
|
8
|
+
# Issue #681's `{Prism::DefNode => Module.nesting}` table as a {DeclarationWalk} collector: the chain every
|
|
9
|
+
# `def` is declared under, keyed by node identity. A top-level `def` records the empty chain (#716); a `def`
|
|
10
|
+
# below a header that renders no name records nothing, because no chain survives it.
|
|
11
|
+
class DefNestingsCollector
|
|
12
|
+
include DeclarationWalk::Collector
|
|
13
|
+
|
|
14
|
+
# The rules `walk_def_nestings` answered differently from the walk, kept so the table stays
|
|
15
|
+
# byte-identical (ADR-116 WD5). Each is a legacy answer, and #1521 tracks converging it:
|
|
16
|
+
#
|
|
17
|
+
# - `factory_block: :ordinary_call` — no bare-factory arm, so a `self::` header in a `Class.new { … }`
|
|
18
|
+
# block anchors on the enclosing `self` and the block's parameters are walked (#1521 item 8).
|
|
19
|
+
# - `lexical_prefix: :nesting_head` — the walker threads no qualified prefix; its meta-new and eval
|
|
20
|
+
# splits resolve against `nesting.first` split into segments, which differs from the walk's prefix
|
|
21
|
+
# below a compact header and below an unnameable cref (#1521 item 1).
|
|
22
|
+
# - `unrendered_header: :body_with_lost_nesting` — a header that renders no name (a parse error) walks
|
|
23
|
+
# its body alone, with the chain lost unless an unnameable cref encloses it (#1521 item 3).
|
|
24
|
+
VARIANTS = {
|
|
25
|
+
factory_block: :ordinary_call,
|
|
26
|
+
lexical_prefix: :nesting_head,
|
|
27
|
+
unrendered_header: :body_with_lost_nesting
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@table = {}.compare_by_identity
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Nothing below a `def` declares a class or module, so the walk goes no further in for this table.
|
|
35
|
+
def on_def(node, context)
|
|
36
|
+
nesting = context.nesting
|
|
37
|
+
@table[node] = nesting unless nesting.nil?
|
|
38
|
+
DeclarationWalk::DECLINE
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def table
|
|
42
|
+
@table.freeze
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
module ScopeIndexer
|
|
6
|
+
# Issue #1507 — a file's issue #681 def-nesting table read over the cross-file seed rather than merged into a
|
|
7
|
+
# copy of it. The copy inserted every `def` in the PROJECT once per analysed file (263 ms of an 18.95 s cold
|
|
8
|
+
# Mastodon run); a lookup probes at most both layers, and only for a `def` whose body is re-walked.
|
|
9
|
+
#
|
|
10
|
+
# Both layers are keyed by node identity, so a same-file declaration and its cross-file twin are distinct keys
|
|
11
|
+
# and the order cannot change an answer. The file layer is read first all the same, which is what the merge it
|
|
12
|
+
# replaces did (`merge!(seed)`, then `merge!(file)`), so the answer holds even if a shared parse ever made the
|
|
13
|
+
# keys overlap.
|
|
14
|
+
#
|
|
15
|
+
# It answers what the table's readers ask — `[]` ({ExpressionTyper#recorded_def_nesting}) and `empty?` — and
|
|
16
|
+
# is deliberately not a Hash: `each`, `key?`, `size` or `==` would have to see both layers, and a Hash holding
|
|
17
|
+
# one of them would answer those wrong instead of raising.
|
|
18
|
+
class LayeredDefNestings
|
|
19
|
+
def initialize(file, seed)
|
|
20
|
+
@file = file
|
|
21
|
+
@seed = seed
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# `fetch` rather than `||`, so presence in the file layer decides, as the later `merge!` did.
|
|
26
|
+
def [](def_node)
|
|
27
|
+
@file.fetch(def_node) { @seed[def_node] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def empty?
|
|
31
|
+
@file.empty? && @seed.empty?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../declaration_walk/traversal"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module ScopeIndexer
|
|
8
|
+
# ADR-48's two member-layout tables as one {DeclarationWalk} collector: the ordered members of each
|
|
9
|
+
# `Data.define` class and of each `Struct.new` class (with its `keyword_init:` flag), in both the subclass
|
|
10
|
+
# form (`class Point < Data.define(:x, :y)`) and the constant form (`Point = Data.define(:x, :y)`). The
|
|
11
|
+
# legacy walkers built the two tables in two walks with the same rules; one collector builds both.
|
|
12
|
+
class MemberLayoutsCollector
|
|
13
|
+
include DeclarationWalk::Collector
|
|
14
|
+
|
|
15
|
+
# The rules the member-layout walkers answered differently from the walk, kept so both tables stay
|
|
16
|
+
# byte-identical (ADR-116 WD5). Each is a legacy answer, and #1521 tracks converging it:
|
|
17
|
+
#
|
|
18
|
+
# - `factory_block: :ordinary_call` — no bare-factory arm, so a `self::` header in a `Class.new { … }`
|
|
19
|
+
# block anchors on the enclosing `self` and the block's parameters are walked (#1521 item 8).
|
|
20
|
+
# - `unrendered_header: :skip` — a header that renders no name (a parse error) stops the walk there:
|
|
21
|
+
# neither its parts nor its body are walked (#1521 item 3).
|
|
22
|
+
VARIANTS = { factory_block: :ordinary_call, unrendered_header: :skip }.freeze
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@data = {}
|
|
26
|
+
@struct = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# A `class Point < Data.define(:x, :y)` header, recorded under the class it names; an unnameable
|
|
30
|
+
# header (`class D` below `class <<` opens `#<Class:C>::D`, which no name spells) records nothing.
|
|
31
|
+
def on_declaration(node, _context, body)
|
|
32
|
+
if node.is_a?(Prism::ClassNode) && !body.singleton_cref
|
|
33
|
+
ScopeIndexer.record_data_member_layout(@data, body.prefix, node.superclass, allow_outer_block: false)
|
|
34
|
+
ScopeIndexer.record_struct_member_layout(@struct, body.prefix, node.superclass, allow_outer_block: false)
|
|
35
|
+
end
|
|
36
|
+
DeclarationWalk::DESCEND
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A `Point = Data.define(:x, :y)` write, recorded under the class the write names. Below an unnameable
|
|
40
|
+
# cref only a path write still names one.
|
|
41
|
+
def on_constant_write(node, context)
|
|
42
|
+
self_owner = context.self_owner
|
|
43
|
+
unless context.singleton_cref && !ScopeIndexer.meta_new_path_target_nameable?(node, self_owner)
|
|
44
|
+
child_prefix = ScopeIndexer.meta_new_child_prefix(node, context.prefix, self_owner)
|
|
45
|
+
end
|
|
46
|
+
if child_prefix
|
|
47
|
+
rvalue = ScopeIndexer.meta_new_rvalue(node)
|
|
48
|
+
ScopeIndexer.record_data_member_layout(@data, child_prefix, rvalue)
|
|
49
|
+
ScopeIndexer.record_struct_member_layout(@struct, child_prefix, rvalue)
|
|
50
|
+
end
|
|
51
|
+
DeclarationWalk::DESCEND
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# `[data_member_layouts, struct_member_layouts]`, each frozen.
|
|
55
|
+
def tables
|
|
56
|
+
[@data.freeze, @struct.freeze]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
module ScopeIndexer
|
|
6
|
+
# The accumulator the mixin walks ({ScopeIndexer#mixin_tables}, {ScopeIndexer#extend_tables}) fill: the
|
|
7
|
+
# per-class module lists as before, plus which mixin edges have no position the tables can vouch for
|
|
8
|
+
# (`Scope::DiscoveryIndex#unpositioned_mixins`).
|
|
9
|
+
#
|
|
10
|
+
# The mixin tables store each class's modules in the order the statements that wrote them run, and that
|
|
11
|
+
# order is a fact only for a DIRECT statement of the class's own body (or of its `class << self` body),
|
|
12
|
+
# in a declaration that is itself a direct statement (a top-level statement, or a direct statement of a
|
|
13
|
+
# direct body): `include M` there runs where it is written, unconditionally. An `include` inside a
|
|
14
|
+
# method, a block (`class_eval`, `Class.new`, `included do`), a conditional, or a declaration wrapped in
|
|
15
|
+
# a conditional or a block (`class C; include B; end if X`) runs when that code runs, if it runs at
|
|
16
|
+
# all, and the receiver form (`Base.prepend(M)`) runs wherever it is called from, so the order the
|
|
17
|
+
# tables give such an edge is a guess. The walks record those edges as they always did and name them
|
|
18
|
+
# here, so a reader that depends on ORDER can decline where the order is not known. An edge written
|
|
19
|
+
# both ways is unpositioned: one guessed copy is enough to move the answer.
|
|
20
|
+
#
|
|
21
|
+
# A mixin call the walks CANNOT record (`include helper`, `include(*MODS)`, `send(:include, M)`,
|
|
22
|
+
# `self.include M`, `C.include(M)`, `singleton_class.include M`, `base.extend M` or
|
|
23
|
+
# `base.class_eval { include M }` in a `self.included(base)` hook, a name in an argument list beside one
|
|
24
|
+
# the walk cannot name) still reshapes the ancestry of the class it belongs to, and
|
|
25
|
+
# leaves the edges the tables did record looking positioned. So it taints the whole owner side with the
|
|
26
|
+
# {WILDCARD} name instead: an owner side that lists {WILDCARD} has no known order at all.
|
|
27
|
+
#
|
|
28
|
+
# The value is `{owner => {include: [names], extend: [names]}}`. `:include` is the instance side
|
|
29
|
+
# (`include` and `prepend`), `:extend` the singleton side (`extend`, and `include` / `prepend` inside
|
|
30
|
+
# `class << self`), so an unknown `extend` never taints a class's instance ancestry.
|
|
31
|
+
#
|
|
32
|
+
# A Hash (owner => lists), so every walk helper that indexes the accumulator keeps working unchanged.
|
|
33
|
+
class MixinAccumulator < Hash
|
|
34
|
+
# The name an owner side lists when a mixin call the walk could not record was written on it.
|
|
35
|
+
WILDCARD = "*"
|
|
36
|
+
|
|
37
|
+
# What {#unpositioned} answers when nothing was noted: one shared frozen table, so the common file
|
|
38
|
+
# allocates none.
|
|
39
|
+
EMPTY = {}.freeze
|
|
40
|
+
|
|
41
|
+
DIRECT_STATEMENTS = [Prism::CallNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode].freeze
|
|
42
|
+
private_constant :DIRECT_STATEMENTS
|
|
43
|
+
|
|
44
|
+
def initialize
|
|
45
|
+
super
|
|
46
|
+
@direct = nil
|
|
47
|
+
@unpositioned = nil
|
|
48
|
+
@hook_params = nil
|
|
49
|
+
@body_blocks = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Marks the statements of a body whose own position is a fact — the calls and declarations in them
|
|
53
|
+
# are the only ones whose position is. The caller marks a declaration's body only when the
|
|
54
|
+
# declaration was itself marked (or is the file's top level).
|
|
55
|
+
def direct_body(body)
|
|
56
|
+
return unless body.is_a?(Prism::StatementsNode)
|
|
57
|
+
|
|
58
|
+
body.body.each do |statement|
|
|
59
|
+
next unless DIRECT_STATEMENTS.any? { |kind| statement.is_a?(kind) }
|
|
60
|
+
|
|
61
|
+
(@direct ||= Set.new.compare_by_identity) << statement
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Marks the block-carrying calls that are statements of a class or module body, whatever that declaration
|
|
66
|
+
# sits in: the only blocks whose `self` the extends walk takes to be the body's own (#1592).
|
|
67
|
+
def body_statements(body)
|
|
68
|
+
return unless body.is_a?(Prism::StatementsNode)
|
|
69
|
+
|
|
70
|
+
body.body.each do |statement|
|
|
71
|
+
if statement.is_a?(Prism::CallNode) && statement.block
|
|
72
|
+
(@body_blocks ||= Set.new.compare_by_identity) << statement
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def body_block?(node)
|
|
78
|
+
!@body_blocks.nil? && @body_blocks.include?(node)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Runs the block with the parameters of a `self.included(base)`-style hook in scope: a mixin call on
|
|
82
|
+
# one of them reshapes the class that reached the hook, which this walk cannot name.
|
|
83
|
+
def with_hook_params(names)
|
|
84
|
+
saved = @hook_params
|
|
85
|
+
@hook_params = saved ? saved + names : names
|
|
86
|
+
yield
|
|
87
|
+
ensure
|
|
88
|
+
@hook_params = saved
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def hook_local?(name)
|
|
92
|
+
!@hook_params.nil? && @hook_params.include?(name)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def direct?(node)
|
|
96
|
+
!@direct.nil? && @direct.include?(node)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Notes the edges `node` recorded for `owner` on `side`. `complete` is false when an argument named
|
|
100
|
+
# nothing the walk could record, which leaves the recorded names' neighbours unknown.
|
|
101
|
+
def note(node, owner, side, targets, complete:)
|
|
102
|
+
return taint(owner, side) unless complete
|
|
103
|
+
return if node.receiver.nil? && direct?(node)
|
|
104
|
+
|
|
105
|
+
add(owner, side, targets)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Names `owner`'s `side` as having no known order.
|
|
109
|
+
def taint(owner, side)
|
|
110
|
+
add(owner, side, [WILDCARD])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# `{owner => {side => [names]}}`, frozen, each list de-duplicated.
|
|
114
|
+
def unpositioned
|
|
115
|
+
return EMPTY if @unpositioned.nil?
|
|
116
|
+
|
|
117
|
+
@unpositioned.transform_values do |sides|
|
|
118
|
+
sides.transform_values { |names| names.uniq.freeze }.freeze
|
|
119
|
+
end.freeze
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def add(owner, side, names)
|
|
125
|
+
((@unpositioned ||= {})[owner] ||= {})[side] = ((@unpositioned[owner][side] || []) + names)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../declaration_walk/traversal"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module ScopeIndexer
|
|
8
|
+
# The as-written superclass table and its issue #682 header-nesting twin (ADR-24 slice 2), as a
|
|
9
|
+
# {DeclarationWalk} collector: each `class Foo < Bar` header's superclass name and the `Module.nesting`
|
|
10
|
+
# its header sits in, plus the superclass of a `Class.new(Parent) { … }` block under that anonymous
|
|
11
|
+
# class's synthetic name (#319). One walk builds both tables, so a caller never pairs a superclass
|
|
12
|
+
# with a nesting from a different parse.
|
|
13
|
+
class SuperclassesCollector
|
|
14
|
+
include DeclarationWalk::Collector
|
|
15
|
+
|
|
16
|
+
# The two rules `walk_class_superclasses` answered differently from the walk, kept so both tables stay
|
|
17
|
+
# byte-identical (ADR-116 WD5). Each is a legacy answer, and #1521 tracks converging it:
|
|
18
|
+
#
|
|
19
|
+
# - `factory_block: :ordinary_call` — the legacy walker had no bare-factory arm, so it walked
|
|
20
|
+
# `Class.new { … }` as any call, block parameters included, with the enclosing `self`. A
|
|
21
|
+
# `class self::E < S` in that block is filed under the enclosing class (`C::E`), a class Ruby never
|
|
22
|
+
# creates (#1521 item 8).
|
|
23
|
+
# - `anonymous_class_path: :outside_class_bodies` — the legacy walker dropped the file path when it
|
|
24
|
+
# entered a class/module, meta-new or eval-family body, but not a `class <<` body, so an anonymous
|
|
25
|
+
# class created inside a class body is keyed without the path the other tables give it (#1521 item
|
|
26
|
+
# 11).
|
|
27
|
+
VARIANTS = { factory_block: :ordinary_call, anonymous_class_path: :outside_class_bodies }.freeze
|
|
28
|
+
|
|
29
|
+
def initialize
|
|
30
|
+
@accumulator = { superclasses: {}, header_nestings: {} }
|
|
31
|
+
# Read through the class, as the walk reads `factory_block`, so a subclass that overrides `VARIANTS`
|
|
32
|
+
# gets one answer from both.
|
|
33
|
+
@path_variant = DeclarationWalk::Collector.variant_of(self.class, :anonymous_class_path)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A header that still names its class records its ancestry: an unnameable one (`class D` below
|
|
37
|
+
# `class <<`) would publish facts for a `C::D` Ruby never creates. The nesting recorded is the one
|
|
38
|
+
# OUTSIDE the header, because the header is evaluated before its body is entered.
|
|
39
|
+
def on_declaration(node, context, body)
|
|
40
|
+
unless body.singleton_cref
|
|
41
|
+
ScopeIndexer.record_declaration_ancestry(node, context.nesting, body.prefix, @accumulator)
|
|
42
|
+
end
|
|
43
|
+
DeclarationWalk::DESCEND
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Only a `Class.new(Parent) { … }` with a literal block records anything, so a call without one is
|
|
47
|
+
# passed over before the path is looked up.
|
|
48
|
+
def on_call(node, context)
|
|
49
|
+
if node.block.is_a?(Prism::BlockNode)
|
|
50
|
+
path = context.anonymous_class_path(@path_variant)
|
|
51
|
+
ScopeIndexer.record_anonymous_meta_superclass(node, @accumulator[:superclasses], path)
|
|
52
|
+
end
|
|
53
|
+
DeclarationWalk::DESCEND
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# `[superclasses, header_nestings]`, each frozen, as {ScopeIndexer.declaration_walk_tables} returns
|
|
57
|
+
# them.
|
|
58
|
+
def tables
|
|
59
|
+
[@accumulator[:superclasses].freeze, @accumulator[:header_nestings].freeze]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|