rigortype 0.4.0 → 0.4.2
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 +4 -3
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- 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_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- 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 +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -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 +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +59 -3
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../source/node_children"
|
|
6
|
+
require_relative "context"
|
|
7
|
+
require_relative "errors"
|
|
8
|
+
require_relative "shadow"
|
|
9
|
+
|
|
10
|
+
module Rigor
|
|
11
|
+
module Inference
|
|
12
|
+
# ADR-116 WD5 (ADR-53 Theme B): one traversal owns the declaration-context rules that `ScopeIndexer`'s table
|
|
13
|
+
# walkers each copied — what `self`, the cref and `Module.nesting` become under `class`/`module`, `class <<`,
|
|
14
|
+
# a `K = Class.new { … }`-family write, a bare factory block, and an eval-family block. A discovery table
|
|
15
|
+
# becomes a collector that receives events and never tracks context itself; the {Context} it is handed is
|
|
16
|
+
# the walk's.
|
|
17
|
+
#
|
|
18
|
+
# Require `rigor/inference/declaration_walk`, not this file: the rules the walk applies are `ScopeIndexer`
|
|
19
|
+
# functions, and that entry point loads them.
|
|
20
|
+
#
|
|
21
|
+
# The arms, in the order the walk tries them:
|
|
22
|
+
#
|
|
23
|
+
# - `class` / `module` — {Collector#on_declaration}, then the body under {Context#declaration_body}. The
|
|
24
|
+
# header's constant path and superclass expression are not walked. A header that renders no name raises
|
|
25
|
+
# no event and follows the `unrendered_header` rule (below).
|
|
26
|
+
# - `class << expr` — the expression under the enclosing context, the body under
|
|
27
|
+
# {Context#singleton_class_body}.
|
|
28
|
+
# - a constant write (the four spellings that can name a class) — {Collector#on_constant_write}; then, when
|
|
29
|
+
# the rvalue is the meta-new idiom, ONLY the factory call's receiver and arguments, under the enclosing
|
|
30
|
+
# context, and its block body, rebound to the class the write names. Nothing else of the write is
|
|
31
|
+
# walked: not the target's parent (`P` in `P::K = …`), not the guard's read in `K = K || …`, not a
|
|
32
|
+
# `.freeze` tail's call, not the block's parameters, and the factory call itself raises no
|
|
33
|
+
# {Collector#on_call}. A write whose rvalue is not the idiom walks its children, as any node does.
|
|
34
|
+
# - `def` — {Collector#on_def}, then the children.
|
|
35
|
+
# - a call — {Collector#on_call}; then a bare factory block (`Class.new { … }`, `Module.new`, `Struct.new`,
|
|
36
|
+
# `Data.define`) walks its receiver and arguments under the enclosing context and its body with an
|
|
37
|
+
# unnamed `self`, and an eval-family block (`class_eval`, `module_eval`, `class_exec`, `module_exec`,
|
|
38
|
+
# `instance_eval`, `instance_exec`) its receiver and arguments under the enclosing context and its body
|
|
39
|
+
# with `self` rebound to the receiver. Neither walks the block's parameters. Any other call walks its
|
|
40
|
+
# children. A collector following the `:ordinary_call` variant of the `factory_block` rule (below) sees
|
|
41
|
+
# a bare factory block walked as any other call instead.
|
|
42
|
+
#
|
|
43
|
+
# Every other node walks its children under the context it was reached with, in `compact_child_nodes`
|
|
44
|
+
# order, so a collector sees its events in the order the legacy walker it replaces accumulated them.
|
|
45
|
+
#
|
|
46
|
+
# ## The collector protocol
|
|
47
|
+
#
|
|
48
|
+
# A collector includes {Collector} and overrides the events it needs. Each handler answers {DESCEND} or
|
|
49
|
+
# {DECLINE}: {DECLINE} takes the node's whole subtree away from THIS collector, while the other collectors
|
|
50
|
+
# of the same run continue into it. Any other answer descends. The walk dispatches only the events some
|
|
51
|
+
# collector of the run overrides.
|
|
52
|
+
#
|
|
53
|
+
# ## Variants
|
|
54
|
+
#
|
|
55
|
+
# Where the legacy walkers disagree on a context rule, a port keeps its walker's answer by naming a
|
|
56
|
+
# variant of the rule in its class's `VARIANTS` (ADR-116 WD5; {RULE_VARIANTS} lists them). Collectors on
|
|
57
|
+
# different variants of a rule still share a run: where the variants give a subtree different contexts
|
|
58
|
+
# (a bare factory block, a meta-new or eval-family body, a header that renders no name) the walk goes down
|
|
59
|
+
# once per variant in use, each collector only in its own, and once for all of them everywhere else. A
|
|
60
|
+
# variant is a legacy answer kept on purpose, and #1521 tracks converging each one.
|
|
61
|
+
#
|
|
62
|
+
# The rule walk stays separate (ADR-53 rejected folding rule collectors into indexing): this walk only
|
|
63
|
+
# builds discovery tables.
|
|
64
|
+
module DeclarationWalk
|
|
65
|
+
# A handler's answers: go on into the node, or leave its subtree to the run's other collectors.
|
|
66
|
+
DESCEND = :descend
|
|
67
|
+
DECLINE = :decline
|
|
68
|
+
|
|
69
|
+
# Each context rule a collector may follow a legacy variant of, with its variants. The first is the
|
|
70
|
+
# walk's own rule, which a collector follows unless its `VARIANTS` names another.
|
|
71
|
+
#
|
|
72
|
+
# - `factory_block` — a bare `Class.new { … }` / `Module.new` / `Struct.new` / `Data.define` block.
|
|
73
|
+
# `:unnamed_self` walks the factory's receiver and arguments, then the body with an unnamed `self`,
|
|
74
|
+
# and skips the block's parameters (`walk_class_cvars`' rule). `:ordinary_call` walks the call's
|
|
75
|
+
# children like any call's — receiver, arguments, the block's parameters and body — under the
|
|
76
|
+
# enclosing context, so a `self::` header in the body anchors on the enclosing self
|
|
77
|
+
# (`walk_class_superclasses`' rule; `class C; Class.new { class self::E < S; end }; end` files
|
|
78
|
+
# `C::E`, a class Ruby never creates; #1521 item 8).
|
|
79
|
+
# - `anonymous_class_path` — the file path an anonymous class's synthetic name carries; answered by
|
|
80
|
+
# {Context#anonymous_class_path}, which documents `:whole_file` and `:outside_class_bodies` (#1521
|
|
81
|
+
# item 11).
|
|
82
|
+
# - `unrendered_header` — a `class` / `module` whose header renders no name, which only a parse error
|
|
83
|
+
# produces (`class foo`, a `module` keyword followed by a `def`). `:children` walks every child, the
|
|
84
|
+
# header's parts included, under the enclosing context (`walk_class_superclasses`' rule).
|
|
85
|
+
# `:skip` walks nothing below it (the member-layout walkers'). `:body_with_lost_nesting` walks the body
|
|
86
|
+
# alone under {Context#lost_header_body} (`walk_def_nestings`'). #1521 item 3.
|
|
87
|
+
# - `lexical_prefix` — the prefix meta-new and eval-family splits resolve against, answered by
|
|
88
|
+
# {Context#lexical_prefix}: `:prefix` or `walk_def_nestings`' `:nesting_head` (#1521 item 1).
|
|
89
|
+
RULE_VARIANTS = {
|
|
90
|
+
factory_block: %i[unnamed_self ordinary_call].freeze,
|
|
91
|
+
anonymous_class_path: %i[whole_file outside_class_bodies].freeze,
|
|
92
|
+
unrendered_header: %i[children skip body_with_lost_nesting].freeze,
|
|
93
|
+
lexical_prefix: %i[prefix nesting_head].freeze
|
|
94
|
+
}.freeze
|
|
95
|
+
|
|
96
|
+
# The event handlers a collector may override; each answers {DESCEND} until overridden.
|
|
97
|
+
module Collector
|
|
98
|
+
EVENTS = %i[on_declaration on_def on_call on_constant_write].freeze
|
|
99
|
+
|
|
100
|
+
# The legacy variants of {RULE_VARIANTS} this collector follows, `rule => variant`. A collector
|
|
101
|
+
# overrides the constant, documenting each entry where it declares it.
|
|
102
|
+
VARIANTS = {}.freeze
|
|
103
|
+
|
|
104
|
+
# A `class` / `module` header. `body` is the context its body is walked under; it exists even when
|
|
105
|
+
# the declaration has no body.
|
|
106
|
+
def on_declaration(_node, _context, _body)
|
|
107
|
+
DESCEND
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def on_def(_node, _context)
|
|
111
|
+
DESCEND
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Every call, the factory and eval-family calls included, before the walk enters its block.
|
|
115
|
+
def on_call(_node, _context)
|
|
116
|
+
DESCEND
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# `K = …`, `P::K = …`, `K ||= …` and `P::K ||= …`, before the walk decides whether the rvalue opens a
|
|
120
|
+
# meta-new body.
|
|
121
|
+
def on_constant_write(_node, _context)
|
|
122
|
+
DESCEND
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# The {EVENTS} `klass` overrides. Answered once per class: the table is a module ivar, which only the
|
|
126
|
+
# main Ractor may touch, so a pool worker on the Ractor backend recomputes it instead (the #1055
|
|
127
|
+
# pattern in `FactStore::Target.local`); the answer is the same either way.
|
|
128
|
+
def self.events_of(klass)
|
|
129
|
+
return overridden_events(klass) unless Ractor.main?
|
|
130
|
+
|
|
131
|
+
@events_by_class[klass] ||= overridden_events(klass)
|
|
132
|
+
end
|
|
133
|
+
@events_by_class = {}.compare_by_identity
|
|
134
|
+
|
|
135
|
+
def self.overridden_events(klass)
|
|
136
|
+
EVENTS.reject { |event| klass.instance_method(event).owner.equal?(self) }.freeze
|
|
137
|
+
end
|
|
138
|
+
private_class_method :overridden_events
|
|
139
|
+
|
|
140
|
+
# The variant of `rule` `klass` follows: its `VARIANTS` entry, or the walk's own rule.
|
|
141
|
+
def self.variant_of(klass, rule)
|
|
142
|
+
klass::VARIANTS.fetch(rule) { RULE_VARIANTS.fetch(rule).first }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Raises {UnknownVariant} unless every `VARIANTS` entry of `klass` names a rule and one of its
|
|
146
|
+
# variants, so a misspelt variant is a {ContractError} rather than the walk's rule followed silently:
|
|
147
|
+
# an error row on the file in a file's own index, and an aborted run in the project pre-pass.
|
|
148
|
+
def self.check_variants!(klass)
|
|
149
|
+
klass::VARIANTS.each do |rule, variant|
|
|
150
|
+
next if RULE_VARIANTS.fetch(rule, EMPTY).include?(variant)
|
|
151
|
+
|
|
152
|
+
raise UnknownVariant, "#{klass}: no #{rule.inspect} variant #{variant.inspect} (ADR-116 WD5)"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
EMPTY = [].freeze
|
|
156
|
+
private_constant :EMPTY
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
NO_COLLECTORS = [].freeze
|
|
160
|
+
private_constant :NO_COLLECTORS
|
|
161
|
+
|
|
162
|
+
module_function
|
|
163
|
+
|
|
164
|
+
# Walks `root` once for every collector, from `context` (a file's top level by default).
|
|
165
|
+
def run(root, collectors, context = Context.root)
|
|
166
|
+
Traversal.for(collectors).walk(root, collectors, context)
|
|
167
|
+
collectors
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# One run's dispatch: which events any collector overrides, so an event none of them handles costs no
|
|
171
|
+
# call per node.
|
|
172
|
+
class Traversal
|
|
173
|
+
# The traversal for `collectors`. A single-collector run's depends on the collector's class alone, so
|
|
174
|
+
# the main Ractor builds it once per class (the #1055 pattern in `FactStore::Target.local`); every
|
|
175
|
+
# other run builds its own.
|
|
176
|
+
def self.for(collectors)
|
|
177
|
+
return new(collectors) unless collectors.size == 1 && Ractor.main?
|
|
178
|
+
|
|
179
|
+
@single_runs[collectors.first.class] ||= new(collectors)
|
|
180
|
+
end
|
|
181
|
+
@single_runs = {}.compare_by_identity
|
|
182
|
+
|
|
183
|
+
def initialize(collectors)
|
|
184
|
+
@declarations = handles?(collectors, :on_declaration)
|
|
185
|
+
@defs = handles?(collectors, :on_def)
|
|
186
|
+
@calls = handles?(collectors, :on_call)
|
|
187
|
+
@constant_writes = handles?(collectors, :on_constant_write)
|
|
188
|
+
collectors.each { |collector| Collector.check_variants!(collector.class) }
|
|
189
|
+
@ordinary_factories = collectors.any? { |collector| ordinary_factory?(collector) }
|
|
190
|
+
@unnamed_factories = !collectors.all? { |collector| ordinary_factory?(collector) }
|
|
191
|
+
@unrendered_variants = collectors.any? { |collector| variant(collector, :unrendered_header) != :children }
|
|
192
|
+
@nesting_heads = collectors.any? { |collector| variant(collector, :lexical_prefix) == :nesting_head }
|
|
193
|
+
# Only a multi-collector run keeps its collectors, which a cached single-collector traversal must
|
|
194
|
+
# not hold on to, and the subsets it builds from them as it first needs each ({#subset}).
|
|
195
|
+
if collectors.size > 1
|
|
196
|
+
@run = collectors
|
|
197
|
+
@subsets = {}
|
|
198
|
+
end
|
|
199
|
+
freeze
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def walk(node, collectors, context) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
203
|
+
return unless node.is_a?(Prism::Node)
|
|
204
|
+
|
|
205
|
+
# The arms are disjoint node classes, so their order is only a cost: the commonest kind is tried first.
|
|
206
|
+
case node
|
|
207
|
+
when Prism::CallNode
|
|
208
|
+
collectors = descending(collectors) { |c| c.on_call(node, context) } if @calls
|
|
209
|
+
return if collectors.empty?
|
|
210
|
+
# Both block arms need a literal block, which most calls lack.
|
|
211
|
+
return if node.block.is_a?(Prism::BlockNode) &&
|
|
212
|
+
(walk_factory_call?(node, collectors, context) || walk_eval_call?(node, collectors, context))
|
|
213
|
+
when Prism::DefNode
|
|
214
|
+
collectors = descending(collectors) { |c| c.on_def(node, context) } if @defs
|
|
215
|
+
return if collectors.empty?
|
|
216
|
+
when Prism::ClassNode, Prism::ModuleNode
|
|
217
|
+
return if walk_declaration?(node, collectors, context)
|
|
218
|
+
when Prism::SingletonClassNode
|
|
219
|
+
return walk_singleton_class(node, collectors, context)
|
|
220
|
+
when Prism::ConstantWriteNode, Prism::ConstantPathWriteNode,
|
|
221
|
+
Prism::ConstantOrWriteNode, Prism::ConstantPathOrWriteNode
|
|
222
|
+
collectors = descending(collectors) { |c| c.on_constant_write(node, context) } if @constant_writes
|
|
223
|
+
return if collectors.empty? || walk_meta_new_write?(node, collectors, context)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
node.rigor_each_child { |child| walk(child, collectors, context) }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
private
|
|
230
|
+
|
|
231
|
+
def handles?(collectors, event)
|
|
232
|
+
collectors.any? { |collector| Collector.events_of(collector.class).include?(event) }
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# A subset of a multi-collector run that its nodes keep asking for, built the first time and reused:
|
|
236
|
+
# each collector alone (a pair in which one declines), the run without each collector (a larger run in
|
|
237
|
+
# which one declines), and the run split by `factory_block` and `lexical_prefix` variant where it mixes
|
|
238
|
+
# them. `key` is a Symbol for a split, or `[kind, collector]` for the other two.
|
|
239
|
+
def subset(kind, collector = nil)
|
|
240
|
+
table = (@subsets[kind] ||= {}.compare_by_identity)
|
|
241
|
+
key = collector || kind
|
|
242
|
+
table.fetch(key) { table[key] = yield.freeze }
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def alone(collector)
|
|
246
|
+
subset(:alone, collector) { [collector] }
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def variant_split(rule, &)
|
|
250
|
+
subset(rule) do
|
|
251
|
+
second, first = @run.partition(&)
|
|
252
|
+
[first.freeze, second.freeze]
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def variant(collector, rule)
|
|
257
|
+
Collector.variant_of(collector.class, rule)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def ordinary_factory?(collector)
|
|
261
|
+
variant(collector, :factory_block) == :ordinary_call
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# The collectors that let the walk into the node the block asks each of them about. Every collector is
|
|
265
|
+
# asked, in order, whatever the others answer.
|
|
266
|
+
#
|
|
267
|
+
# Asking allocates nothing, so a node where no collector declines costs no object. Where exactly one
|
|
268
|
+
# declines, the answer is a {#subset} memoised for the run (the one survivor of a pair, or the whole run
|
|
269
|
+
# without the decliner), built the first time and free after that. Two cases copy instead: a decline
|
|
270
|
+
# inside a group of three or more that is not the whole run (a variant fork's group, or what is left of
|
|
271
|
+
# a run of four or more after an earlier decline), and two or more declines at one node of a group of
|
|
272
|
+
# three or more. Each allocates one Array.
|
|
273
|
+
def descending(collectors, &)
|
|
274
|
+
case collectors.size
|
|
275
|
+
when 0 then collectors
|
|
276
|
+
when 1 then yield(collectors.first) == DECLINE ? NO_COLLECTORS : collectors
|
|
277
|
+
when 2 then descending_pair(collectors, yield(collectors.first), yield(collectors.last))
|
|
278
|
+
else descending_many(collectors, &)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# An index loop rather than `each_with_index`, which allocates a block frame object (a T_IMEMO) per
|
|
283
|
+
# call: this runs at every node a run of three or more dispatches an event for.
|
|
284
|
+
def descending_many(collectors)
|
|
285
|
+
declined = nil
|
|
286
|
+
kept = nil
|
|
287
|
+
index = 0
|
|
288
|
+
while index < collectors.size
|
|
289
|
+
collector = collectors[index]
|
|
290
|
+
if yield(collector) != DECLINE
|
|
291
|
+
kept&.push(collector)
|
|
292
|
+
elsif declined.nil? && kept.nil?
|
|
293
|
+
declined = index
|
|
294
|
+
else
|
|
295
|
+
kept ||= collectors.take(index).tap { |list| list.delete_at(declined) }
|
|
296
|
+
end
|
|
297
|
+
index += 1
|
|
298
|
+
end
|
|
299
|
+
kept || (declined.nil? ? collectors : without(collectors, declined))
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def without(collectors, index)
|
|
303
|
+
declined = collectors[index]
|
|
304
|
+
if collectors.equal?(@run)
|
|
305
|
+
return subset(:without, declined) { @run.reject { |collector| collector.equal?(declined) } }
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
collectors.dup.tap { |list| list.delete_at(index) }
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def descending_pair(collectors, first, last)
|
|
312
|
+
if first == DECLINE
|
|
313
|
+
last == DECLINE ? NO_COLLECTORS : alone(collectors.last)
|
|
314
|
+
else
|
|
315
|
+
last == DECLINE ? alone(collectors.first) : collectors
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# False only for a header that renders no prefix while every collector here follows the walk's
|
|
320
|
+
# `unrendered_header` rule, which then walks the node's children like any node's.
|
|
321
|
+
def walk_declaration?(node, collectors, context)
|
|
322
|
+
body_context = context.declaration_body(node)
|
|
323
|
+
return walk_unrendered_header?(node, collectors, context) if body_context.nil?
|
|
324
|
+
|
|
325
|
+
collectors = descending(collectors) { |c| c.on_declaration(node, context, body_context) } if @declarations
|
|
326
|
+
body = node.body
|
|
327
|
+
walk(body, collectors, body_context) if body && !collectors.empty?
|
|
328
|
+
true
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# The `unrendered_header` rule: `:children` collectors walk every child under the enclosing context,
|
|
332
|
+
# `:body_with_lost_nesting` ones the body alone under {Context#lost_header_body}, `:skip` ones nothing.
|
|
333
|
+
def walk_unrendered_header?(node, collectors, context)
|
|
334
|
+
return false unless @unrendered_variants
|
|
335
|
+
|
|
336
|
+
children = collectors.select { |collector| variant(collector, :unrendered_header) == :children }
|
|
337
|
+
node.rigor_each_child { |child| walk(child, children, context) } unless children.empty?
|
|
338
|
+
lost = collectors.select { |collector| variant(collector, :unrendered_header) == :body_with_lost_nesting }
|
|
339
|
+
walk(node.body, lost, context.lost_header_body) if node.body && !lost.empty?
|
|
340
|
+
true
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def walk_singleton_class(node, collectors, context)
|
|
344
|
+
walk(node.expression, collectors, context)
|
|
345
|
+
body = node.body
|
|
346
|
+
walk(body, collectors, context.singleton_class_body) if body
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def walk_meta_new_write?(node, collectors, context)
|
|
350
|
+
enclosing, body, body_self = context.meta_new_split(node)
|
|
351
|
+
return false if enclosing.nil?
|
|
352
|
+
|
|
353
|
+
enclosing.each { |part| walk(part, collectors, context) }
|
|
354
|
+
walk_rebound_body(node, body, collectors, context, body_self, :meta_new) if body
|
|
355
|
+
true
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# A meta-new or eval-family body, with `self` rebound to `owner`. The `lexical_prefix` rule decides
|
|
359
|
+
# `owner`, so where the run mixes its variants the `:nesting_head` collectors take the owner their
|
|
360
|
+
# prefix gives, and the body is walked once per distinct owner — once in all where the two agree.
|
|
361
|
+
def walk_rebound_body(node, body, collectors, context, owner, kind)
|
|
362
|
+
return walk(body, collectors, rebound(context, owner, kind)) unless @nesting_heads
|
|
363
|
+
|
|
364
|
+
prefixes, heads = prefix_groups(collectors)
|
|
365
|
+
head_owner = heads.empty? ? owner : split(node, context, kind, :nesting_head)[2]
|
|
366
|
+
if head_owner == owner
|
|
367
|
+
walk(body, collectors, rebound(context, owner, kind))
|
|
368
|
+
else
|
|
369
|
+
walk(body, prefixes, rebound(context, owner, kind)) unless prefixes.empty?
|
|
370
|
+
walk(body, heads, rebound(context, head_owner, kind))
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def rebound(context, owner, kind)
|
|
375
|
+
kind == :eval ? context.eval_body(owner) : context.meta_new_body(owner)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def split(node, context, kind, lexical_variant)
|
|
379
|
+
kind == :eval ? context.eval_split(node, lexical_variant) : context.meta_new_split(node, lexical_variant)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# `[prefix, nesting_head]` collectors of a run that mixes the `lexical_prefix` variants.
|
|
383
|
+
def prefix_groups(collectors)
|
|
384
|
+
if collectors.equal?(@run)
|
|
385
|
+
return variant_split(:lexical_prefix) { |collector| variant(collector, :lexical_prefix) == :nesting_head }
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
heads, prefixes = collectors.partition { |collector| variant(collector, :lexical_prefix) == :nesting_head }
|
|
389
|
+
[prefixes, heads]
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# The `factory_block` rule, for a call with a literal block. False when every collector here follows
|
|
393
|
+
# `:ordinary_call`, so the call walks its children like any call. Otherwise the receiver and arguments
|
|
394
|
+
# are walked once, for every collector, since both variants walk them under the enclosing context;
|
|
395
|
+
# the variants part only at the block: the `:unnamed_self` collectors walk its body with an unnamed
|
|
396
|
+
# `self`, the `:ordinary_call` ones the whole block, parameters included, under the enclosing context.
|
|
397
|
+
# Each collector sees the same events in the same order as in a run of its own.
|
|
398
|
+
def walk_factory_call?(node, collectors, context)
|
|
399
|
+
return false unless @unnamed_factories && ScopeIndexer.meta_new_constant_rvalue?(node)
|
|
400
|
+
|
|
401
|
+
unnamed = unnamed_factory_group(collectors)
|
|
402
|
+
return false if unnamed.empty?
|
|
403
|
+
|
|
404
|
+
walk(node.receiver, collectors, context)
|
|
405
|
+
node.arguments&.arguments&.each { |argument| walk(argument, collectors, context) }
|
|
406
|
+
block = node.block
|
|
407
|
+
walk(block.body, unnamed, context.factory_body) if block.body
|
|
408
|
+
walk(block, ordinary_factory_group(collectors), context) if unnamed.size < collectors.size
|
|
409
|
+
true
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def unnamed_factory_group(collectors)
|
|
413
|
+
return collectors unless @ordinary_factories
|
|
414
|
+
return factory_split.first if collectors.equal?(@run)
|
|
415
|
+
|
|
416
|
+
collectors.reject { |collector| ordinary_factory?(collector) }
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def ordinary_factory_group(collectors)
|
|
420
|
+
return factory_split.last if collectors.equal?(@run)
|
|
421
|
+
|
|
422
|
+
collectors.select { |collector| ordinary_factory?(collector) }
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# `[unnamed_self, ordinary_call]` collectors of the whole run.
|
|
426
|
+
def factory_split
|
|
427
|
+
variant_split(:factory_block) { |collector| ordinary_factory?(collector) }
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def walk_eval_call?(node, collectors, context)
|
|
431
|
+
enclosing, body, eval_self = context.eval_split(node)
|
|
432
|
+
return false if enclosing.nil?
|
|
433
|
+
|
|
434
|
+
enclosing.each { |part| walk(part, collectors, context) }
|
|
435
|
+
walk_rebound_body(node, body, collectors, context, eval_self, :eval) if body
|
|
436
|
+
true
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ADR-116 WD5 — the entry point for {Rigor::Inference::DeclarationWalk}. The walk lives in
|
|
4
|
+
# `declaration_walk/traversal`, and the context rules it applies are `ScopeIndexer` functions, so this file loads
|
|
5
|
+
# the indexer, which loads the walk. `ScopeIndexer` itself requires the traversal file rather than this one, which
|
|
6
|
+
# keeps the two free of a require cycle.
|
|
7
|
+
require_relative "scope_indexer"
|
|
@@ -33,7 +33,7 @@ module Rigor
|
|
|
33
33
|
# ({.rehydrated_nesting}).
|
|
34
34
|
def self.with_run
|
|
35
35
|
previous = Thread.current[MEMO_KEY]
|
|
36
|
-
Thread.current[MEMO_KEY] = { nodes: {}, indexes: {}, nestings: {}.compare_by_identity }
|
|
36
|
+
Thread.current[MEMO_KEY] = { nodes: {}, indexes: {}, nestings: {}.compare_by_identity, refinement_queries: {} }
|
|
37
37
|
yield
|
|
38
38
|
ensure
|
|
39
39
|
Thread.current[MEMO_KEY] = previous
|
|
@@ -76,8 +76,9 @@ module Rigor
|
|
|
76
76
|
# walk over the analyzer's own parse produced. This holds per NODE and needs nothing about files.
|
|
77
77
|
# 2. AGREEMENT — where both tables answer for the same DEF (through different objects), they answer the
|
|
78
78
|
# same chain. A bundle is only reused when the file's content SHA-256 matches
|
|
79
|
-
# ({Cache::FileDigest.hexdigest}, the digest tier, not the stat one), and
|
|
80
|
-
# pure function of that file's AST, so identical bytes
|
|
79
|
+
# ({Cache::FileDigest.hexdigest}, the digest tier, not the stat one), and the def-nesting table
|
|
80
|
+
# (`ScopeIndexer.declaration_walk_tables`) is a pure function of that file's AST, so identical bytes
|
|
81
|
+
# yield an identical chain.
|
|
81
82
|
#
|
|
82
83
|
# What is NOT true — and was asserted here before it was measured — is that a file takes exactly ONE of
|
|
83
84
|
# the two branches per run. It does so in the cross-file pre-pass only. An UNCHANGED file re-analysed as
|
|
@@ -112,6 +113,28 @@ module Rigor
|
|
|
112
113
|
end
|
|
113
114
|
private_class_method :record_nesting
|
|
114
115
|
|
|
116
|
+
# Issue #1664 — the {InEffectRefinements} over `path`'s own parse, once a run, or nil when the file cannot be
|
|
117
|
+
# read: the typed refined arm reads another file's refine body through it. The bodies' `Module.nesting`
|
|
118
|
+
# is recorded as a handle's is, so the re-typed body reads its constants where it is written.
|
|
119
|
+
def self.refinement_query(path)
|
|
120
|
+
memo = Thread.current[MEMO_KEY]
|
|
121
|
+
return build_refinement_query(path, nil) if memo.nil?
|
|
122
|
+
|
|
123
|
+
queries = memo[:refinement_queries]
|
|
124
|
+
return queries[path] if queries.key?(path)
|
|
125
|
+
|
|
126
|
+
queries[path] = build_refinement_query(path, memo)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.build_refinement_query(path, memo)
|
|
130
|
+
query = InEffectRefinements.new(Prism.parse(File.read(path)).value)
|
|
131
|
+
query.refine_def_nestings.each { |node, nesting| record_nesting(memo, node, nesting) } if memo
|
|
132
|
+
query
|
|
133
|
+
rescue StandardError
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
private_class_method :build_refinement_query
|
|
137
|
+
|
|
115
138
|
# Finds the node for `handle` using a per-file `{node_id => DefNode}` + `{name => DefNode}` index cache.
|
|
116
139
|
def self.locate(handle, index_cache)
|
|
117
140
|
index = index_cache[handle.path] ||= build_file_index(handle.path)
|