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
|
@@ -34,6 +34,7 @@ module Rigor
|
|
|
34
34
|
:discovered_includes,
|
|
35
35
|
:discovered_prepends,
|
|
36
36
|
:discovered_extends,
|
|
37
|
+
:unpositioned_mixins,
|
|
37
38
|
:discovered_class_sources,
|
|
38
39
|
:constant_sources,
|
|
39
40
|
:constant_writers,
|
|
@@ -49,7 +50,14 @@ module Rigor
|
|
|
49
50
|
:patched_line_readers,
|
|
50
51
|
:clears_last_status,
|
|
51
52
|
:defines_case_equality,
|
|
52
|
-
:implicit_self_evidence
|
|
53
|
+
:implicit_self_evidence,
|
|
54
|
+
:in_effect_refinements,
|
|
55
|
+
:possible_discovered_methods,
|
|
56
|
+
:possible_discovered_deferred_ranges,
|
|
57
|
+
:contested_discovered_def_nodes,
|
|
58
|
+
:contested_discovered_singleton_def_nodes,
|
|
59
|
+
:contested_discovered_method_visibilities,
|
|
60
|
+
:contested_discovered_parameter_envelopes
|
|
53
61
|
)
|
|
54
62
|
|
|
55
63
|
class DiscoveryIndex
|
|
@@ -58,6 +66,115 @@ module Rigor
|
|
|
58
66
|
EMPTY_NAME_SET = Set.new.freeze
|
|
59
67
|
private_constant :EMPTY_NODE_TABLE, :EMPTY_TABLE, :EMPTY_NAME_SET
|
|
60
68
|
|
|
69
|
+
# ADR-119 WD1 — each member that may one day admit `possible` or `contested` facts, and the sibling that
|
|
70
|
+
# holds them. Every copy path reads this one declaration, so a member never travels without its sibling:
|
|
71
|
+
# {#with} raises on a half pair and {.compact_pairs} drops a pair only when BOTH halves are empty. No
|
|
72
|
+
# producer fills a sibling yet (the lane-1 carriage), so every sibling is empty and each member keeps its
|
|
73
|
+
# meaning, the union of its certain and possible facts.
|
|
74
|
+
#
|
|
75
|
+
# - `possible_*` has its member's shape and is a subset of it (a `discovered_methods` pair is a
|
|
76
|
+
# `{class => {name => kind}}` table).
|
|
77
|
+
# - `contested_*` is a Set of key paths into its member, one per entry whose value depends on a possible
|
|
78
|
+
# fact: `[class, method]`, or `[class, [kind, method]]` for an envelope.
|
|
79
|
+
#
|
|
80
|
+
# The members without a sibling here (`discovered_def_sources`, `discovered_singleton_def_sources`) are site
|
|
81
|
+
# tables (dependency edges, positions, fingerprints): no read takes a definer, visibility, arity or type from
|
|
82
|
+
# them, and certainty does not move a site. The reads whose answer depends on the site are
|
|
83
|
+
# `Scope#same_file_top_level_def?` (file identity, independent of certainty), `#singleton_def_shadows_call?`
|
|
84
|
+
# and `#instance_def_shadows_call?` (a conditional def's site answers "shadows"). Where "shadows" narrows an
|
|
85
|
+
# answer, the caller pairs it with {#possible_method?}: existence is the method table, certainty is the possible
|
|
86
|
+
# table alone (ADR-119 C1d-c).
|
|
87
|
+
SIBLINGS = {
|
|
88
|
+
discovered_methods: :possible_discovered_methods,
|
|
89
|
+
discovered_deferred_ranges: :possible_discovered_deferred_ranges,
|
|
90
|
+
discovered_def_nodes: :contested_discovered_def_nodes,
|
|
91
|
+
discovered_singleton_def_nodes: :contested_discovered_singleton_def_nodes,
|
|
92
|
+
discovered_method_visibilities: :contested_discovered_method_visibilities,
|
|
93
|
+
discovered_parameter_envelopes: :contested_discovered_parameter_envelopes
|
|
94
|
+
}.freeze
|
|
95
|
+
|
|
96
|
+
# Either half of a pair to the other.
|
|
97
|
+
PARTNERS = SIBLINGS.flat_map { |member, sibling| [[member, sibling], [sibling, member]] }.to_h.freeze
|
|
98
|
+
private_constant :PARTNERS
|
|
99
|
+
|
|
100
|
+
# [#1507](https://github.com/rigortype/rigor/issues/1507) — the kind of fact each member holds, which decides
|
|
101
|
+
# what a reader may conclude from an entry and which gate covers the member (ADR-119 WD4, proposed). Each
|
|
102
|
+
# reason describes the table as the code builds it today. `spec/rigor/declaration_facts/member_classes_spec.rb`
|
|
103
|
+
# fails on a member that is unclassified or classified twice, and checks each class's shape on an index built
|
|
104
|
+
# from a fixture project; it pins the few misfilings those checks accept.
|
|
105
|
+
#
|
|
106
|
+
# - `:set_valued` — names, edges, files or rows whose presence is the fact. A reader asks whether an entry is
|
|
107
|
+
# there.
|
|
108
|
+
# - `:single_valued` — one value per key, which readers use; disagreeing contributions fold by a fixed rule.
|
|
109
|
+
# - `:typed` — the values are types, which already carry uncertainty in the type lattice.
|
|
110
|
+
# - `:syntactic` — read off the parse of the analysed file alone, which is their reference.
|
|
111
|
+
# - `:run_state` — the run's own state, not a fact about the program.
|
|
112
|
+
# - `:sibling` — the `possible_*` / `contested_*` half of a pair in {SIBLINGS}, which travels with its member.
|
|
113
|
+
MEMBER_CLASSES = {
|
|
114
|
+
set_valued: {
|
|
115
|
+
discovered_classes: "the class and module names the project declares; the value is the name's singleton",
|
|
116
|
+
discovered_methods: "which names each class answers, and on which side",
|
|
117
|
+
discovered_refinements: "the methods a refine block adds, keyed by the refined class",
|
|
118
|
+
discovered_global_write_census: "the names any file aliases, defines or mixes in at the top level",
|
|
119
|
+
discovered_includes: "the modules each class includes, prepends among them",
|
|
120
|
+
discovered_prepends: "the modules each class prepends",
|
|
121
|
+
discovered_extends: "the modules each singleton mixes in; the module itself for module_function",
|
|
122
|
+
unpositioned_mixins: "per side, the mixin edges each class records whose order is not a fact, or \"*\"",
|
|
123
|
+
discovered_class_sources: "the files that declare each class, reopenings included",
|
|
124
|
+
discovered_deferred_ranges: "each file's def and block ranges; def rows add owner and module_function side",
|
|
125
|
+
constant_sources: "the files that write each constant",
|
|
126
|
+
constant_writers: "the constant names some write binds, by last segment",
|
|
127
|
+
constant_shadowers: "the constant names a non-memo write assigns, by last segment",
|
|
128
|
+
published_constant_names: "the last segments of the constants the project publishes",
|
|
129
|
+
local_constant_names: "the last segments of the constants the analysed file assigns",
|
|
130
|
+
published_constant_alias_names: "the file's constants copied straight from a published one",
|
|
131
|
+
published_constant_ivars: "the ivars each class seeds from a published constant"
|
|
132
|
+
}.freeze,
|
|
133
|
+
single_valued: {
|
|
134
|
+
discovered_def_nodes: "the def node each instance method resolves to",
|
|
135
|
+
discovered_singleton_def_nodes: "the def node each singleton method resolves to",
|
|
136
|
+
discovered_def_sources: "the first-seen path:line of each instance method, which readers use",
|
|
137
|
+
discovered_singleton_def_sources: "the first-seen path:line of each singleton method, which readers use",
|
|
138
|
+
discovered_method_visibilities: "each method's visibility",
|
|
139
|
+
discovered_parameter_envelopes: "each method's parameter shape, opaque when contributions disagree",
|
|
140
|
+
discovered_superclasses: "each class's superclass as written",
|
|
141
|
+
discovered_header_nestings: "the cref each ancestor name resolves in, an alternatives list when ambiguous",
|
|
142
|
+
data_member_layouts: "each Data class's member layout",
|
|
143
|
+
struct_member_layouts: "each Struct class's member layout"
|
|
144
|
+
}.freeze,
|
|
145
|
+
typed: {
|
|
146
|
+
declared_types: "the type each declaration node binds",
|
|
147
|
+
class_ivars: "the joined type of each ivar's writes, per class",
|
|
148
|
+
class_cvars: "the joined type of each cvar's writes, per class",
|
|
149
|
+
program_globals: "the joined type of each global's writes",
|
|
150
|
+
program_global_seeds: "a declared global's signature type joined with its writes",
|
|
151
|
+
in_source_constants: "the type each constant's write binds",
|
|
152
|
+
param_inferred_types: "the union of each parameter's call-site argument types"
|
|
153
|
+
}.freeze,
|
|
154
|
+
syntactic: {
|
|
155
|
+
discovered_def_nestings: "the Module.nesting each def is written in, keyed by node identity",
|
|
156
|
+
patched_line_readers: "the gets / readline names the file defines through the define_method family",
|
|
157
|
+
clears_last_status: "whether the analysed file holds a call that may set $? to nil",
|
|
158
|
+
defines_case_equality: "whether the analysed file holds a define_method naming ===",
|
|
159
|
+
in_effect_refinements: "the file's ordered in-effect refinements; built lazily from its tree, never seeded",
|
|
160
|
+
implicit_self_evidence: "where the file's implicit-self readers sit; built lazily from its tree, never seeded"
|
|
161
|
+
}.freeze,
|
|
162
|
+
run_state: {
|
|
163
|
+
run_generation: "the identity token of the current run"
|
|
164
|
+
}.freeze,
|
|
165
|
+
sibling: {
|
|
166
|
+
possible_discovered_methods: "the names and sides of discovered_methods that only a possible definer gives",
|
|
167
|
+
possible_discovered_deferred_ranges: "the rows of discovered_deferred_ranges that only a possible fact gives",
|
|
168
|
+
contested_discovered_def_nodes: "the key paths of discovered_def_nodes that a possible definer may change",
|
|
169
|
+
contested_discovered_singleton_def_nodes:
|
|
170
|
+
"the key paths of discovered_singleton_def_nodes that a possible definer may change",
|
|
171
|
+
contested_discovered_method_visibilities:
|
|
172
|
+
"the key paths of discovered_method_visibilities that a possible fact may change",
|
|
173
|
+
contested_discovered_parameter_envelopes:
|
|
174
|
+
"the key paths of discovered_parameter_envelopes that a possible fact may change"
|
|
175
|
+
}.freeze
|
|
176
|
+
}.freeze
|
|
177
|
+
|
|
61
178
|
# The third value a `discovered_methods` entry can hold, beside `:instance` and `:singleton`. One name may
|
|
62
179
|
# legitimately be defined on both sides of the same class (`def helper` plus a `class << self` twin), and the
|
|
63
180
|
# table is keyed by name alone — so before this existed the second `def` overwrote the first's kind and
|
|
@@ -89,6 +206,9 @@ module Rigor
|
|
|
89
206
|
# `Source::ParameterEnvelope::OPAQUE`, so a bucket folds under the one join every other entry does.
|
|
90
207
|
# Symbols, because every per-method key is an Array and neither can collide with the other.
|
|
91
208
|
#
|
|
209
|
+
# The class marks below and {ENVELOPE_PROJECT_WIDE} are presence facts read only to decline a read; they are
|
|
210
|
+
# never a contested path. Contested envelope paths are always `[class, [kind, name]]`.
|
|
211
|
+
#
|
|
92
212
|
# - {ENVELOPE_MODULE_MARK}: the name is declared with `module` (or `Const = Module.new do … end`) somewhere
|
|
93
213
|
# in the project. An instance of it is an instance of an unknown includer.
|
|
94
214
|
# - {ENVELOPE_DYNAMIC_MARK}: the class body rewrites its method table in a way no literal argument names
|
|
@@ -99,12 +219,36 @@ module Rigor
|
|
|
99
219
|
# - {ENVELOPE_REFINED_MARK}: a `refine` block refines the class, which redefines its methods wherever a `using`
|
|
100
220
|
# is in effect. Issue #1367 splits it from {ENVELOPE_DYNAMIC_MARK}, which it used to share: an implicit
|
|
101
221
|
# conversion (`to_str`, `to_int`) ignores a refinement, so the `global.*` rules must tell the two apart.
|
|
102
|
-
#
|
|
222
|
+
# The readers that ask whether a surface is rewritten read both ({.rewritten_surface?}), except
|
|
223
|
+
# `call.wrong-arity`'s source envelope (`CheckRules::SourceArity`), which leaves a refinement to the per-call
|
|
224
|
+
# decline that knows the name and the `using` (issue #1797).
|
|
225
|
+
# Issue #1820 — the kind of a `[ENVELOPE_REMOVED_KIND, method_name]` bucket entry: the project removes the name
|
|
226
|
+
# from the class with a literal `undef`, `remove_method` or `undef_method`, whichever side. A removal is not
|
|
227
|
+
# otherwise distinguishable from an `attr_*` or `define_method`, whose envelope is opaque too, and the
|
|
228
|
+
# refinement own-definer proof must not take a removed method as the one Ruby finds.
|
|
229
|
+
ENVELOPE_REMOVED_KIND = :removed
|
|
230
|
+
# Issue #1820 — a `K = Class.new(Parent)` constant write: the class's superclass is not in the superclass table
|
|
231
|
+
# (the meta-new walk does not route `Parent` through ancestor discovery), so a reader that must place `K` in a
|
|
232
|
+
# hierarchy reads its superclass as unknown, as it reads the `nil` the table records for `class K < <expr>`.
|
|
233
|
+
ENVELOPE_META_SUPERCLASS_MARK = :"<meta-superclass>"
|
|
103
234
|
ENVELOPE_MODULE_MARK = :"<module>"
|
|
104
235
|
ENVELOPE_DYNAMIC_MARK = :"<dynamic>"
|
|
105
236
|
ENVELOPE_OBJECT_EXTENDED_MARK = :"<object-extended>"
|
|
106
237
|
ENVELOPE_REFINED_MARK = :"<refined>"
|
|
107
238
|
|
|
239
|
+
# Issue #1820 — whether an envelope bucket says the project declares, or rewrites, the name's method table: a
|
|
240
|
+
# per-method entry, the {ENVELOPE_MODULE_MARK} a `module` declaration records, or the {ENVELOPE_DYNAMIC_MARK}
|
|
241
|
+
# (`Comparable.module_eval { define_method(computed) }` gives a module a method no table names, so a reader
|
|
242
|
+
# must keep it on its chain rather than read RBS's surface for it). A bucket that holds only the refined,
|
|
243
|
+
# object-extended or meta-superclass marks is evidence about a name the project may only mention, such as
|
|
244
|
+
# `Comparable` in `x.singleton_class.prepend(Comparable)`.
|
|
245
|
+
DECLARING_MARKS = [ENVELOPE_MODULE_MARK, ENVELOPE_DYNAMIC_MARK].freeze
|
|
246
|
+
private_constant :DECLARING_MARKS
|
|
247
|
+
|
|
248
|
+
def self.declares_surface?(bucket)
|
|
249
|
+
!bucket.nil? && bucket.any? { |key, _| !key.is_a?(Symbol) || DECLARING_MARKS.include?(key) }
|
|
250
|
+
end
|
|
251
|
+
|
|
108
252
|
# Whether an envelope bucket says the project rewrites the class's method table beyond its literal names: a
|
|
109
253
|
# dynamic rewrite, or a refinement.
|
|
110
254
|
def self.rewritten_surface?(bucket)
|
|
@@ -118,6 +262,14 @@ module Rigor
|
|
|
118
262
|
# present. Not a constant character, so no class can be named it.
|
|
119
263
|
ENVELOPE_PROJECT_WIDE = "<project-wide>"
|
|
120
264
|
|
|
265
|
+
# ADR-121 WD7 (issues #1796, #1799) — the wildcard of `discovered_refinements`, standing for what the walk could
|
|
266
|
+
# not read. As a refined-class key it is a class the walk cannot name (`refine(k)`, `refine(self)`); as a method
|
|
267
|
+
# key, names a refine body defines that the walk cannot enumerate (`import_methods`, a computed
|
|
268
|
+
# `define_method`); as a refining module, a module the walk cannot name (a `refine` in an instance method,
|
|
269
|
+
# which any module extending it may run). `{"*" => {"*" => [M]}}` makes `M` opaque: some `refine` of `M` is
|
|
270
|
+
# unreadable. No constant can be named `*`, and method keys are Symbols, so neither axis collides.
|
|
271
|
+
REFINEMENT_WILDCARD = "*"
|
|
272
|
+
|
|
121
273
|
# The shared all-empty index `Scope.empty` (and every scope that never sees a seeding pass) points at — one
|
|
122
274
|
# allocation per process.
|
|
123
275
|
EMPTY = new(
|
|
@@ -210,6 +362,24 @@ module Rigor
|
|
|
210
362
|
# the table inside the indexer and threw it away; `Narrowing` needs it to survive onto the scope,
|
|
211
363
|
# because `Singleton[C]`'s ancestry is exactly what `extend` writes and nothing else records it.
|
|
212
364
|
discovered_extends: EMPTY_TABLE,
|
|
365
|
+
# `{qualified class or module name => {include: [names], extend: [names]}}` — the mixin edges of the three
|
|
366
|
+
# tables above whose ORDER is not a fact, per side (`:include` is the instance side, `include` and
|
|
367
|
+
# `prepend`; `:extend` the singleton side). An edge is listed when it is written anywhere but as a direct
|
|
368
|
+
# statement of the class's own body (or its `class << self` body) in a declaration that is itself a
|
|
369
|
+
# direct statement — inside a `def`, a block, a conditional, or a conditional or deferred declaration —
|
|
370
|
+
# or in the receiver form, and when it shares an argument list with a name the walk could not record
|
|
371
|
+
# (`Inference::ScopeIndexer::MixinAccumulator`). A mixin call the walk cannot record at all (`include
|
|
372
|
+
# helper`, `include(*MODS)`, `send(:include, M)`, `self.include M`, `C.include(M)`,
|
|
373
|
+
# `singleton_class.include M`, a call on a `self.included(base)`-style hook's parameter, an `*_eval` block on
|
|
374
|
+
# one or on an opaque receiver whose body mixes a module in) lists the name
|
|
375
|
+
# `"*"` on the side it reshapes: that side of the owner has no known order. An edge written both ways
|
|
376
|
+
# is listed. Folded by union. A reader that depends on the order of a class's ancestors MUST decline
|
|
377
|
+
# when the owner it asks about, or any ancestor it walks through, lists anything on the side it reads;
|
|
378
|
+
# the three tables themselves are unchanged.
|
|
379
|
+
unpositioned_mixins: EMPTY_TABLE,
|
|
380
|
+
# The files that declare each class, reopenings included. Seeded on every run: the ADR-46 recording
|
|
381
|
+
# accessors read it for their class edges, and an ancestor-order reader reads whether a class is
|
|
382
|
+
# declared in more than one file, where the order of its mixins across the files is load order.
|
|
213
383
|
discovered_class_sources: EMPTY_TABLE,
|
|
214
384
|
# Issue #644 — `{qualified constant name => Set[declaring file]}`, the write attribution behind the
|
|
215
385
|
# cross-file value-constant table. Read only by `Scope#record_constant_dependency` during ADR-46
|
|
@@ -280,8 +450,93 @@ module Rigor
|
|
|
280
450
|
# (`Inference::LastLine::SelfEvidence`, read on the first ask), which `Inference::LastLine.reads_line?` reads
|
|
281
451
|
# them by. Filled by `Inference::ScopeIndexer.index` from the file's own tree only; nil, where no file was
|
|
282
452
|
# indexed, declines every such reader.
|
|
283
|
-
implicit_self_evidence: nil
|
|
453
|
+
implicit_self_evidence: nil,
|
|
454
|
+
# Issue #1673 (ADR-121 WD1) — the file's in-effect refinements (`Inference::InEffectRefinements`, walked on the
|
|
455
|
+
# first ask), which `Scope#in_effect_refinements` reads for the typer and the check rules read off the root's
|
|
456
|
+
# scope. Filled by `Inference::ScopeIndexer.index` from the file's own tree only; nil, where no file was
|
|
457
|
+
# indexed, answers no lexical refinement.
|
|
458
|
+
in_effect_refinements: nil,
|
|
459
|
+
# ADR-119 WD1 — the siblings of the members {SIBLINGS} pairs, empty until a producer admits a possible fact.
|
|
460
|
+
possible_discovered_methods: EMPTY_TABLE,
|
|
461
|
+
possible_discovered_deferred_ranges: EMPTY_TABLE,
|
|
462
|
+
contested_discovered_def_nodes: EMPTY_NAME_SET,
|
|
463
|
+
contested_discovered_singleton_def_nodes: EMPTY_NAME_SET,
|
|
464
|
+
contested_discovered_method_visibilities: EMPTY_NAME_SET,
|
|
465
|
+
contested_discovered_parameter_envelopes: EMPTY_NAME_SET
|
|
284
466
|
)
|
|
467
|
+
|
|
468
|
+
# The siblings of an all-empty index, `{sibling name => empty value}`, frozen: what a def-index with no
|
|
469
|
+
# possible fact carries, and a Runner's initial state. Copy it (`dup`) before mutating.
|
|
470
|
+
EMPTY_SIBLINGS = SIBLINGS.each_value.to_h { |sibling| [sibling, EMPTY.public_send(sibling)] }.freeze
|
|
471
|
+
|
|
472
|
+
# The all-empty value of a paired member or sibling.
|
|
473
|
+
def self.empty_value(name)
|
|
474
|
+
EMPTY.public_send(name)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def self.empty_siblings = EMPTY_SIBLINGS
|
|
478
|
+
|
|
479
|
+
# ADR-119 WD1 — `tables` (a `with(**)` argument Hash) with every pair either complete or absent: a pair whose
|
|
480
|
+
# halves are both missing, nil or empty is dropped, any other pair carries BOTH halves (the half that was
|
|
481
|
+
# absent or nil becomes its empty value). Dropping members one at a time would strand a non-empty sibling,
|
|
482
|
+
# and passing a half would make {#with} raise. Entries outside {SIBLINGS} pass through untouched.
|
|
483
|
+
def self.compact_pairs(tables)
|
|
484
|
+
tables.each_with_object({}) do |(name, table), out|
|
|
485
|
+
partner = PARTNERS[name]
|
|
486
|
+
next out[name] = table if partner.nil?
|
|
487
|
+
next if out.key?(name)
|
|
488
|
+
|
|
489
|
+
other = tables[partner]
|
|
490
|
+
next if blank_table?(table) && blank_table?(other)
|
|
491
|
+
|
|
492
|
+
out[name] = table.nil? ? empty_value(name) : table
|
|
493
|
+
out[partner] = other.nil? ? empty_value(partner) : other
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Whether `name` is a member or sibling of {SIBLINGS}.
|
|
498
|
+
def self.paired?(name)
|
|
499
|
+
PARTNERS.key?(name)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def self.blank_table?(table)
|
|
503
|
+
table.nil? || table.empty?
|
|
504
|
+
end
|
|
505
|
+
private_class_method :blank_table?
|
|
506
|
+
|
|
507
|
+
# The six siblings as `{sibling name => table}`, for a caller that rebuilds the paired members and carries
|
|
508
|
+
# their siblings over unchanged.
|
|
509
|
+
def sibling_tables
|
|
510
|
+
SIBLINGS.each_value.to_h { |sibling| [sibling, public_send(sibling)] }
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# ADR-119 WD1/WD2 — whether `class_name` answers `method_name` on `kind` only through a `possible` fact
|
|
514
|
+
# (`possible_discovered_methods`, empty until a producer admits one). Internal to the candidate-set read.
|
|
515
|
+
def possible_method?(class_name, method_name, kind)
|
|
516
|
+
recorded = possible_discovered_methods[class_name.to_s]&.[](method_name.to_sym)
|
|
517
|
+
recorded == kind || (!recorded.nil? && recorded == METHOD_KIND_BOTH)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# ADR-119 WD1 — whether `path` (`[class, name]`, or `[class, [kind, name]]` for an envelope) is a contested
|
|
521
|
+
# entry of `member`, one of the single-valued members {SIBLINGS} pairs. Internal to the candidate-set read.
|
|
522
|
+
def contested?(member, path)
|
|
523
|
+
public_send(SIBLINGS.fetch(member)).include?(path)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Data's `with`, except that a paired member and its sibling are accepted only TOGETHER (ADR-119 WD1): a
|
|
527
|
+
# caller that rebuilt one without the other would leave the sibling describing the old table, so it
|
|
528
|
+
# raises instead. A call with no change returns the receiver.
|
|
529
|
+
def with(**changes)
|
|
530
|
+
return self if changes.empty?
|
|
531
|
+
|
|
532
|
+
changes.each_key do |name|
|
|
533
|
+
partner = PARTNERS[name]
|
|
534
|
+
next if partner.nil? || changes.key?(partner)
|
|
535
|
+
|
|
536
|
+
raise ArgumentError, "DiscoveryIndex#with: #{name} and #{partner} change together; #{partner} is missing"
|
|
537
|
+
end
|
|
538
|
+
super
|
|
539
|
+
end
|
|
285
540
|
end
|
|
286
541
|
end
|
|
287
542
|
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
class Scope
|
|
5
|
+
class ResolutionChain
|
|
6
|
+
# ADR-119 WD2 — per-name relevance, the narrowing of an unsettled chain (#1591). A {Mark} says a node's mixin
|
|
7
|
+
# edges have no known order; the chain still answers a question about ONE method name when the module the
|
|
8
|
+
# mark names cannot answer that name, because with no fork every entry reached the chain by a single route,
|
|
9
|
+
# so a module of unknown presence or position adds or removes only its own closure's entries and moves no
|
|
10
|
+
# other entry's first occurrence. A closure that cannot answer the name therefore cannot move its first
|
|
11
|
+
# definer. {ResolutionChain#settle} asks it under `unknown_for:`; nothing else does.
|
|
12
|
+
#
|
|
13
|
+
# - A named entry `Q` (not `"*"`) is discharged for `n` when every entry of `Q`'s own instance chain (its
|
|
14
|
+
# closure) is clean: (i) a project entry, or an external one RBS knows whose declaration lacks `n`
|
|
15
|
+
# (the test `SourceArity#external_mixin_lacks_method?` applies); (ii) no project entry carries the dynamic
|
|
16
|
+
# mark, lists `"*"` on the instance side or the mark's, or records `method_missing`; (iii) no project
|
|
17
|
+
# entry records `n` in `discovered_methods` (either kind) or in `discovered_method_visibilities`.
|
|
18
|
+
# - A multi-file mark names no entry: it is discharged when at most one of the node's edges' closures is
|
|
19
|
+
# not clean. A mark that is both listed and multi-file must pass both rules.
|
|
20
|
+
# - An ambiguous spelling (`:arity` only: several project modules a compact-header collision left as the
|
|
21
|
+
# spelling's meaning, #986) is discharged when every one of them is clean; any candidate that fails fails it.
|
|
22
|
+
# - `"*"` and a truncated closure are never discharged.
|
|
23
|
+
#
|
|
24
|
+
# The verdict and the edges it read are memoised per `[mark, name]` in the flavor's bucket and the edges are
|
|
25
|
+
# replayed on every call while a dependency recording is active (ADR-46): per tested project entry its class
|
|
26
|
+
# edge and the negative method edge on `Owner#name`, per tested external the negative class edge on the last
|
|
27
|
+
# segment of its spelling, so a new file declaring the module re-checks the consumer.
|
|
28
|
+
module Relevance
|
|
29
|
+
WILDCARD = "*"
|
|
30
|
+
|
|
31
|
+
# The singleton hooks Ruby calls while a module enters (or is asked to enter) another's ancestry. A module
|
|
32
|
+
# that defines one can add methods to the includer, extender or prepender with nothing the tables record
|
|
33
|
+
# on the module (`def self.included(base) = base.attr_reader(:foo)`), so no closure holding one can be
|
|
34
|
+
# said not to answer a name.
|
|
35
|
+
HOOKS = %i[included extended prepended inherited append_features extend_object prepend_features].freeze
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
def discharged?(scope, chain, mark, name)
|
|
40
|
+
name = name.to_sym
|
|
41
|
+
memo = ResolutionChain.relevance_memo(scope, chain.flavor)
|
|
42
|
+
verdict, edges = memo[[mark, name]] ||= compute(scope, chain.flavor, mark, name)
|
|
43
|
+
replay(scope, edges) if Analysis::DependencyRecorder.active?
|
|
44
|
+
verdict
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def compute(scope, flavor, mark, name)
|
|
48
|
+
context = Context.new(scope, flavor, mark, name)
|
|
49
|
+
verdict = !mark.listed.include?(WILDCARD) && listed_discharged?(context) && multi_file_discharged?(context)
|
|
50
|
+
[verdict, context.edges.uniq.freeze].freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def listed_discharged?(context)
|
|
54
|
+
mark = context.mark
|
|
55
|
+
mark.listed.all? { |raw| context.closure_clean?(raw) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def multi_file_discharged?(context)
|
|
59
|
+
mark = context.mark
|
|
60
|
+
return true unless mark.multi_file
|
|
61
|
+
|
|
62
|
+
edges = context.edge_names
|
|
63
|
+
edges.count { |raw| !context.closure_clean?(raw) } <= 1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def replay(scope, edges)
|
|
67
|
+
edges.each do |kind, value|
|
|
68
|
+
case kind
|
|
69
|
+
when :class then ResolutionChain.record_class(scope, value)
|
|
70
|
+
when :method then Analysis::DependencyRecorder.read_missing(:method, value)
|
|
71
|
+
when :external then Analysis::DependencyRecorder.read_missing(:class, value)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# True only for an ancestor the project does not declare that RBS knows and whose declaration lacks `name`
|
|
77
|
+
# (`SourceArity#external_mixin_lacks_method?`); an unknown one, or one that has it, may answer.
|
|
78
|
+
def external_lacks?(scope, candidates, name)
|
|
79
|
+
known = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: scope) }
|
|
80
|
+
return false if known.nil?
|
|
81
|
+
|
|
82
|
+
Rigor::Reflection.instance_method_definition(known, name, scope: scope).nil?
|
|
83
|
+
rescue StandardError
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# One verdict's working state: the scope, the resolver, and the edges the tests read.
|
|
88
|
+
class Context
|
|
89
|
+
attr_reader :mark, :edges
|
|
90
|
+
|
|
91
|
+
def initialize(scope, flavor, mark, name)
|
|
92
|
+
@scope = scope
|
|
93
|
+
@flavor = flavor
|
|
94
|
+
@mark = mark
|
|
95
|
+
@name = name
|
|
96
|
+
@resolver = ResolutionChain.resolver_for(scope, flavor)
|
|
97
|
+
@edges = []
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The raw names of the node's own edges on the side the mark covers.
|
|
101
|
+
def edge_names
|
|
102
|
+
discovery = @scope.discovery
|
|
103
|
+
table = @mark.kind == :extend ? discovery.discovered_extends : discovery.discovered_includes
|
|
104
|
+
table[@mark.node] || EMPTY_NAMES
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Whether the closure of the module `raw` names from the marked node cannot answer the name.
|
|
108
|
+
def closure_clean?(raw)
|
|
109
|
+
resolved = @resolver.resolve(@mark.node, raw)
|
|
110
|
+
case resolved
|
|
111
|
+
when String then project_closure_clean?(resolved)
|
|
112
|
+
when Array then resolved.all? { |name| project_closure_clean?(name) }
|
|
113
|
+
else external_clean?(@resolver.candidates(@mark.node, raw), raw)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def project_closure_clean?(module_name)
|
|
120
|
+
chain = ResolutionChain.for(@scope, module_name, :instance, @flavor)
|
|
121
|
+
return false if chain.truncated?
|
|
122
|
+
|
|
123
|
+
chain.entries.all? do |entry|
|
|
124
|
+
entry.external? ? external_clean?(entry.candidates, entry.raw) : project_clean?(entry)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def project_clean?(entry)
|
|
129
|
+
owner = entry.name
|
|
130
|
+
@edges << [:class, owner] << [:method, "#{owner}##{@name}"]
|
|
131
|
+
return false if Scope::DiscoveryIndex.rewritten_surface?(@scope.parameter_envelopes_of(owner))
|
|
132
|
+
return false if wildcard_listed?(owner)
|
|
133
|
+
return false if @scope.discovered_method?(owner, :method_missing, :instance)
|
|
134
|
+
return false if HOOKS.any? { |hook| @scope.discovered_method?(owner, hook, :singleton) }
|
|
135
|
+
|
|
136
|
+
!records_name?(owner)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def wildcard_listed?(owner)
|
|
140
|
+
sides = @scope.discovery.unpositioned_mixins[owner]
|
|
141
|
+
return false if sides.nil?
|
|
142
|
+
|
|
143
|
+
[:include, @mark.kind].uniq.any? { |side| sides[side]&.include?(WILDCARD) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def records_name?(owner)
|
|
147
|
+
@scope.discovered_method?(owner, @name, :instance) || @scope.discovered_method?(owner, @name, :singleton) ||
|
|
148
|
+
!@scope.discovered_method_visibility(owner, @name).nil?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# An ancestor the project does not declare: only a module RBS knows, whose declaration lacks the name,
|
|
152
|
+
# is evidence it does not define it (`SourceArity#external_mixin_lacks_method?`).
|
|
153
|
+
def external_clean?(candidates, raw)
|
|
154
|
+
@edges << [:external, raw.to_s.split("::").last]
|
|
155
|
+
Relevance.external_lacks?(@scope, candidates, @name)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
private_constant :Context
|
|
159
|
+
|
|
160
|
+
EMPTY_NAMES = [].freeze
|
|
161
|
+
private_constant :EMPTY_NAMES
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|