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,253 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
# ADR-119 WD3 (errata 2026-10-08, PR C2-a) — the singleton side's positional hook decline. A hook's edge on
|
|
6
|
+
# the singleton side is recorded on no includer, so no chain state carries it (WD3); what the read CAN say
|
|
7
|
+
# is where such an edge could land. A hook touches the singleton chain of a class `K` in two ways only:
|
|
8
|
+
#
|
|
9
|
+
# - (U1) `K.extend(X)`, `class_methods do`, a concern's `ClassMethods`: Ruby inserts `X` after `#<Class:K>`
|
|
10
|
+
# and before `#<Class:K.superclass>`, inside `K`'s own level;
|
|
11
|
+
# - (U2) `included do def self.x end`, `define_singleton_method`, `singleton_class.class_eval`: a definition ON
|
|
12
|
+
# `#<Class:K>`, at the level's head.
|
|
13
|
+
#
|
|
14
|
+
# So a hook at level `i` cannot move a definer at an index at or before the level's head, and a definer found
|
|
15
|
+
# at `#<Class:K_j>` itself, written in `K_j`'s own body, is immune to every hook of levels `j` and deeper. The
|
|
16
|
+
# read's last candidate gives a BOUND: its index when it is that class entry and its `def` was written in
|
|
17
|
+
# `K_j`'s body; one past it for an extended module's entry, or for a copy the extends fold put on `K_j`
|
|
18
|
+
# (the `def`'s recorded nesting does not start with `K_j`), and every entry when nothing answers. The levels
|
|
19
|
+
# tested are those that start before the bound and do not end before `from`. The read declines when a tested
|
|
20
|
+
# level's own instance level (the class, its prepends and includes with their closures) or its singleton
|
|
21
|
+
# segment (the class's singleton and its extends' closures) holds a HOOK-CAPABLE entry, or when a level
|
|
22
|
+
# deeper than the shallowest tested level is hook-capable by the same test: its `inherited` defines on
|
|
23
|
+
# every subclass's singleton, and it need not be the class's own `def`. A deeper external superclass RBS
|
|
24
|
+
# does not know counts, its `inherited` being unknown by construction.
|
|
25
|
+
#
|
|
26
|
+
# Hook-capable, for a project entry or a declared module the chain holds as external (a candidate of its
|
|
27
|
+
# spelling is a `discovered_class_sources`, `discovered_classes`, `discovered_includes` or
|
|
28
|
+
# `discovered_extends` key; an ambiguous spelling tests every declared candidate and any capable one
|
|
29
|
+
# declines): lists `"*"` on either side, records a {Scope::ResolutionChain::Relevance::HOOKS} name on
|
|
30
|
+
# either side or as an envelope key, or extends `ActiveSupport::Concern`. An undeclared external is capable
|
|
31
|
+
# unless RBS knows it (WD2(i)'s limit: an RBS-known external is clean). The dynamic mark is not a hook signal.
|
|
32
|
+
# The verdict does not depend on the name asked, so it is memoised per entry in the chain's flavor bucket
|
|
33
|
+
# with the ADR-46 edges it read (a class edge and the negative class edge on the unqualified name per tested
|
|
34
|
+
# project entry, the negative class edge on the spelling's last segment and the candidates' class edges per
|
|
35
|
+
# tested external), replayed on every call while a recording is active.
|
|
36
|
+
#
|
|
37
|
+
# Known remainders, pinned in `spec/integration/definer_resolution_witness_spec.rb`: `class << self; prepend
|
|
38
|
+
# P` is recorded as an `extend` (the chain places `P` after the singleton, so an own `def self.x` is trusted
|
|
39
|
+
# against it); a concern is capable for every name; an own `def self.x` is trusted against its own level's
|
|
40
|
+
# U2 hooks and same-class redefinitions; a `class Class; def inherited` monkeypatch is unseen; and a
|
|
41
|
+
# superclass that only `extend`s adds two forks, so every read on its subclasses declines.
|
|
42
|
+
module SingletonHookDecline
|
|
43
|
+
WILDCARD = "*"
|
|
44
|
+
CONCERN = "ActiveSupport::Concern"
|
|
45
|
+
SIDES = %i[include extend].freeze
|
|
46
|
+
|
|
47
|
+
module_function
|
|
48
|
+
|
|
49
|
+
# True when a hook may have placed a definer of `method_name` ahead of the read's last candidate on the
|
|
50
|
+
# singleton `chain` (the candidate set `hits`, read from `from`).
|
|
51
|
+
def decline?(scope, chain, method_name, from, hits)
|
|
52
|
+
bound = bound_of(scope, chain, method_name, hits.last)
|
|
53
|
+
tested = tested_levels(chain, from, bound)
|
|
54
|
+
return false if tested.empty?
|
|
55
|
+
return true if chain.truncated?
|
|
56
|
+
|
|
57
|
+
context = Context.new(scope, chain.flavor)
|
|
58
|
+
tested.any? { |level| context.level_capable?(chain, level) } || context.deeper_hook?(chain, tested.first)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def bound_of(scope, chain, method_name, hit)
|
|
62
|
+
return chain.entries.size if hit.nil?
|
|
63
|
+
|
|
64
|
+
index = hit.index
|
|
65
|
+
entry = chain.entries[index]
|
|
66
|
+
# A singleton project entry on a singleton chain is always a level's class entry.
|
|
67
|
+
own = !entry.external? && entry.side == :singleton && def_head(scope, entry.name, method_name) == entry.name
|
|
68
|
+
own ? index : index + 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# What a class entry's answer is when its `def` was written in another module's body: `:copy` when that
|
|
72
|
+
# module is on the same level's singleton segment (the extends fold copied it there; the copy is not where
|
|
73
|
+
# Ruby finds the method — the module's own entry is, and an entry between them may answer first — so the
|
|
74
|
+
# read asks again past it), `:stray` when it is not (the fold and the chain resolved the `extend`'s name to
|
|
75
|
+
# different modules, `extend X` inside `module A` with both `X` and `A::X` declared; or the `def` is not the
|
|
76
|
+
# node that module's instance table holds, a `def C.x` written in its body: the read declines), and
|
|
77
|
+
# nil otherwise (the class's own `def`, no `def`, or one with no recorded nesting).
|
|
78
|
+
def copy_kind(scope, chain, method_name, hit)
|
|
79
|
+
entry = chain.entries[hit.index]
|
|
80
|
+
return nil if entry.external? || entry.side != :singleton
|
|
81
|
+
|
|
82
|
+
head = def_head(scope, entry.name, method_name)
|
|
83
|
+
return nil if head.nil? || head == entry.name
|
|
84
|
+
|
|
85
|
+
level = chain.level_starts.rindex { |start| start <= hit.index }
|
|
86
|
+
return :stray if level.nil? || level >= chain.level_count
|
|
87
|
+
|
|
88
|
+
found = chain.level_entries(level).any? { |candidate| candidate.side == :instance && candidate.name == head }
|
|
89
|
+
found && copied_from?(scope, head, method_name, entry.name) ? :copy : :stray
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Whether the class's singleton `def` is the very node `head`'s instance table holds, the one the fold
|
|
93
|
+
# copied: a receiverless `def`. A `def C.x` or `class << C; def x; end` written inside `head`'s body has the
|
|
94
|
+
# same nesting head and is the class's own, not a copy. (The walk records a `def C.x` written in `module X`
|
|
95
|
+
# as `X#x` too, so identity alone does not tell it from a copy; its receiver does.)
|
|
96
|
+
def copied_from?(scope, head, method_name, owner)
|
|
97
|
+
node = scope.singleton_def_for(owner, method_name)
|
|
98
|
+
return false if node.nil? || !node.receiver.nil?
|
|
99
|
+
|
|
100
|
+
scope.user_def_for(head, method_name).equal?(node)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The innermost `Module.nesting` entry `owner`'s singleton `def` of the name was written in, or nil.
|
|
104
|
+
def def_head(scope, owner, method_name)
|
|
105
|
+
node = scope.singleton_def_for(owner, method_name)
|
|
106
|
+
return nil if node.nil?
|
|
107
|
+
|
|
108
|
+
nesting = scope.discovery.discovered_def_nestings[node] || DefNodeResolver.rehydrated_nesting(node)
|
|
109
|
+
nesting&.first
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def tested_levels(chain, from, bound)
|
|
113
|
+
starts = chain.level_starts
|
|
114
|
+
size = chain.entries.size
|
|
115
|
+
(0...chain.level_count).select { |level| starts[level] < bound && (starts[level + 1] || size) >= from }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The verdicts' working state: the scope, the flavor and the memo the verdicts share.
|
|
119
|
+
class Context
|
|
120
|
+
def initialize(scope, flavor)
|
|
121
|
+
@scope = scope
|
|
122
|
+
@flavor = flavor
|
|
123
|
+
@memo = Scope::ResolutionChain.hook_memo(scope, flavor)
|
|
124
|
+
@recording = Analysis::DependencyRecorder.active?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# A tested level: its singleton segment, and its class's own instance level.
|
|
128
|
+
def level_capable?(chain, level)
|
|
129
|
+
return true if chain.level_entries(level).any? { |entry| capable?(entry) }
|
|
130
|
+
|
|
131
|
+
class_name = chain.level_classes[level]
|
|
132
|
+
!class_name.nil? && instance_level_capable?(class_name)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# A level deeper than `level` whose `inherited` can define on every subclass's singleton: one that is
|
|
136
|
+
# hook-capable as a tested level is. Its class records `inherited` (own, folded, or a literal
|
|
137
|
+
# `define_method`), or an entry of its own instance level or singleton segment can define it unseen (a
|
|
138
|
+
# concern's `class_methods do def inherited`, a hook that extends the class with a module defining it),
|
|
139
|
+
# or it is an external superclass that is hook-capable.
|
|
140
|
+
def deeper_hook?(chain, level)
|
|
141
|
+
((level + 1)...chain.level_count).any? { |deeper| level_capable?(chain, deeper) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def capable?(entry)
|
|
145
|
+
return verdict(:project, entry.name) { project_capable?(entry.name) } unless entry.external?
|
|
146
|
+
|
|
147
|
+
verdict(:external, entry) { external_capable?(entry) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def instance_level_capable?(class_name)
|
|
153
|
+
verdict(:instance_level, class_name) do |edges|
|
|
154
|
+
chain = Scope::ResolutionChain.for(@scope, class_name, :instance, @flavor)
|
|
155
|
+
next true if chain.truncated?
|
|
156
|
+
|
|
157
|
+
entries = chain.level_count.zero? ? chain.entries : chain.level_entries(0)
|
|
158
|
+
entries.any? do |entry|
|
|
159
|
+
edges.concat(entry_edges(entry))
|
|
160
|
+
capable?(entry)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# The memoised `[verdict, edges]` of one test, its edges replayed while a recording is active.
|
|
166
|
+
def verdict(kind, key)
|
|
167
|
+
verdict, edges = @memo[[kind, key]] ||= begin
|
|
168
|
+
edges = []
|
|
169
|
+
value = yield(edges)
|
|
170
|
+
[value ? true : false, edges.concat(own_edges(kind, key)).uniq.freeze].freeze
|
|
171
|
+
end
|
|
172
|
+
replay(edges) if @recording
|
|
173
|
+
verdict
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def own_edges(kind, key)
|
|
177
|
+
return entry_edges(key) if kind == :external
|
|
178
|
+
return EMPTY_EDGES if kind == :instance_level
|
|
179
|
+
|
|
180
|
+
[[:class, key], [:missing, key.to_s.split("::").last]]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def entry_edges(entry)
|
|
184
|
+
return [[:class, entry.name], [:missing, entry.last_segment]] unless entry.external?
|
|
185
|
+
|
|
186
|
+
entry.candidates.map { |candidate| [:class, candidate] } << [:missing, entry.raw.to_s.split("::").last]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def replay(edges)
|
|
190
|
+
edges.each do |kind, value|
|
|
191
|
+
case kind
|
|
192
|
+
when :class then Scope::ResolutionChain.record_class(@scope, value)
|
|
193
|
+
when :missing then Analysis::DependencyRecorder.read_missing(:class, value)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def project_capable?(name)
|
|
199
|
+
sides = @scope.discovery.unpositioned_mixins[name]
|
|
200
|
+
return true if sides && SIDES.any? { |side| sides[side]&.include?(WILDCARD) }
|
|
201
|
+
|
|
202
|
+
return true if records_hook?(name)
|
|
203
|
+
|
|
204
|
+
extends_concern?(name)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Whether `name` records a hook name on either side, or in its envelope table: a `def self.inherited`, a
|
|
208
|
+
# module's instance `def inherited` (a hook once extended), `singleton_class.define_method(:inherited)`
|
|
209
|
+
# (recorded as an instance method) and `define_singleton_method(:inherited)` (recorded only as an opaque
|
|
210
|
+
# envelope, the name-literal evidence `ScopeIndexer#record_surface_evidence` files).
|
|
211
|
+
def records_hook?(name)
|
|
212
|
+
envelopes = @scope.parameter_envelopes_of(name)
|
|
213
|
+
Scope::ResolutionChain::Relevance::HOOKS.any? do |hook|
|
|
214
|
+
@scope.discovered_method?(name, hook, :singleton) || @scope.discovered_method?(name, hook, :instance) ||
|
|
215
|
+
envelopes.key?([:singleton, hook]) || envelopes.key?([:instance, hook])
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Whether `name`'s own singleton segment (its `extend`s, as the chain places them) holds
|
|
220
|
+
# `ActiveSupport::Concern` — the concern shape's only signal, a framework name the follow-up ADR moves
|
|
221
|
+
# behind the plugin API (WD3, Q11).
|
|
222
|
+
def extends_concern?(name)
|
|
223
|
+
chain = Scope::ResolutionChain.for(@scope, name, :singleton, @flavor)
|
|
224
|
+
entries = chain.level_count.zero? ? chain.entries : chain.level_entries(0)
|
|
225
|
+
entries.any? do |entry|
|
|
226
|
+
entry.external? ? entry.candidates.include?(CONCERN) : entry.name == CONCERN
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# A declared module the chain holds as external is tested as that module; an undeclared one is capable
|
|
231
|
+
# unless RBS knows it.
|
|
232
|
+
def external_capable?(entry)
|
|
233
|
+
declared = entry.candidates.select { |candidate| declared?(candidate) }
|
|
234
|
+
return declared.any? { |name| verdict(:project, name) { project_capable?(name) } } unless declared.empty?
|
|
235
|
+
|
|
236
|
+
entry.candidates.none? { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: @scope) }
|
|
237
|
+
rescue StandardError
|
|
238
|
+
true
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def declared?(name)
|
|
242
|
+
discovery = @scope.discovery
|
|
243
|
+
discovery.discovered_class_sources.key?(name) || discovery.discovered_classes.key?(name) ||
|
|
244
|
+
discovery.discovered_includes.key?(name) || discovery.discovered_extends.key?(name)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
private_constant :Context
|
|
248
|
+
|
|
249
|
+
EMPTY_EDGES = [].freeze
|
|
250
|
+
private_constant :EMPTY_EDGES
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|