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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../reflection"
|
|
4
|
+
require_relative "../../inference/definer_resolution"
|
|
4
5
|
require_relative "../../source/parameter_envelope"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
@@ -14,9 +15,10 @@ module Rigor
|
|
|
14
15
|
# Asked in two stages, because every condition past the first can only WITHHOLD a firing:
|
|
15
16
|
#
|
|
16
17
|
# 1. {#owner_envelope} walks the receiver's superclass chain to the first class whose own body, or one of
|
|
17
|
-
# whose project mixins, records the name. Every record at that level must agree —
|
|
18
|
-
#
|
|
19
|
-
# opaque. A call that fits this envelope
|
|
18
|
+
# whose project mixins, records the name. Every record at that level must agree — deliberately wider
|
|
19
|
+
# than Ruby's order, so a mixin whose definition may shadow the class's own, or a load-order-dependent
|
|
20
|
+
# pair (#986), declines instead of answering — and none may be opaque. A call that fits this envelope
|
|
21
|
+
# is silent, and nothing below is consulted.
|
|
20
22
|
# 2. {#authoritative?} runs only for a call that would fire. It declines when a `method_missing` /
|
|
21
23
|
# `respond_to_missing?` hook or a dynamic-surface mark sits anywhere in the chain; when a mixin the
|
|
22
24
|
# project does not declare is not a known RBS module that lacks the name; when a `pre_eval:` patch or
|
|
@@ -24,20 +26,45 @@ module Rigor
|
|
|
24
26
|
# project subclass of the receiver — whose instances the receiver's type admits — records a different
|
|
25
27
|
# or opaque envelope for it.
|
|
26
28
|
#
|
|
29
|
+
# ADR-119 C1b / C2-c — on both sides stage 1 and the subclass check ask `Inference::DefinerResolution`, and a
|
|
30
|
+
# chain it declines (`UNKNOWN`) answers no envelope: where Ruby's order depends on a fact the tables cannot
|
|
31
|
+
# prove, the rule is silent instead of checking the call against master's order (#1570, #1607).
|
|
32
|
+
#
|
|
27
33
|
# Deliberately NOT in scope: a constructor reached through `Class#new` (nothing records `initialize` as
|
|
28
34
|
# `[:singleton, :new]`, so `.new` finds no owner unless the class writes `def self.new` itself); keyword
|
|
29
35
|
# arity (the envelope's required-keyword flag declines, as `compute_arity_envelope` declines an RBS
|
|
30
36
|
# function with required keywords); and a `class_eval` whose receiver is not a constant, which the
|
|
31
37
|
# discovery walk cannot name.
|
|
32
|
-
class SourceArity
|
|
38
|
+
class SourceArity # rubocop:disable Metrics/ClassLength
|
|
33
39
|
MISSING_HOOKS = %i[method_missing respond_to_missing?].freeze
|
|
34
40
|
private_constant :MISSING_HOOKS
|
|
35
41
|
|
|
36
42
|
# One class on the walk: its own name, the project modules its mixins resolve to (transitively through
|
|
37
43
|
# their own `include`s), and the candidate-name lists of the mixins that resolve to no project module.
|
|
44
|
+
#
|
|
45
|
+
# Issue #1570 — the level is a SEGMENT of the receiver's `Scope::ResolutionChain` (`:arity` flavor): the
|
|
46
|
+
# class with the modules Ruby inserts around it before the superclass's entries begin. Ruby skips an
|
|
47
|
+
# `include` of a module the superclass chain already carries, so `class C < Base; include M` where
|
|
48
|
+
# `Base` includes `M` leaves `M` at `Base`'s level; collecting every module a class's own `include`s
|
|
49
|
+
# name put it at `C`'s level instead, where its `def` answered a call `Base#foo` receives.
|
|
38
50
|
Level = Data.define(:class_name, :modules, :externals)
|
|
39
51
|
private_constant :Level
|
|
40
52
|
|
|
53
|
+
# ADR-119 C1b — the answer of a level whose records are not one non-opaque envelope. It equals itself in
|
|
54
|
+
# every world, so two worlds that both reach such a level agree, and no envelope equals it.
|
|
55
|
+
AMBIGUOUS = Object.new.freeze
|
|
56
|
+
private_constant :AMBIGUOUS
|
|
57
|
+
|
|
58
|
+
# What `Inference::DefinerResolution` reads off a candidate: the answer, the entry that gave it, its
|
|
59
|
+
# position on the chain and its side. The same four readers as `DefinerResolution::Hit`, which a call site
|
|
60
|
+
# may not name (the case/in contract spec allows no other reference to the module).
|
|
61
|
+
ArityHit = Data.define(:answer, :owner, :index, :side)
|
|
62
|
+
private_constant :ArityHit
|
|
63
|
+
|
|
64
|
+
# A subclass level the walk cannot read: `subclasses_agree?` reads the nil as a decline.
|
|
65
|
+
NO_LEVELS = [nil].freeze
|
|
66
|
+
private_constant :NO_LEVELS
|
|
67
|
+
|
|
41
68
|
def initialize(scope, method_name, kind)
|
|
42
69
|
@scope = scope
|
|
43
70
|
@method_name = method_name.to_sym
|
|
@@ -53,6 +80,7 @@ module Rigor
|
|
|
53
80
|
def owner_envelope(class_name)
|
|
54
81
|
@owner_entries = []
|
|
55
82
|
@ambiguous = false
|
|
83
|
+
@unknown = false
|
|
56
84
|
envelope, @withheld = Analysis::DependencyRecorder.withhold { walk_to_owner(class_name) }
|
|
57
85
|
envelope
|
|
58
86
|
end
|
|
@@ -67,6 +95,11 @@ module Rigor
|
|
|
67
95
|
# withhold, so none of these needs the file-level class edge the bucket reads would otherwise record.
|
|
68
96
|
def settle_by_definitions
|
|
69
97
|
return if @withheld.nil?
|
|
98
|
+
# ADR-119 C1b — a declined chain (`@unknown`) that some level records the name on is silent only until
|
|
99
|
+
# another file's edit lifts the decline, so its verdict depends on everything the walk read, as an opaque
|
|
100
|
+
# level's does. A decline over a name no project level records is not `@unknown` (see
|
|
101
|
+
# {#decline_unless_recorded_nowhere}): it settles by definitions, as an `ABSENT` read does.
|
|
102
|
+
return settle_by_walk if @unknown
|
|
70
103
|
|
|
71
104
|
@owner_entries.each do |name, kind|
|
|
72
105
|
if kind == :singleton
|
|
@@ -95,20 +128,88 @@ module Rigor
|
|
|
95
128
|
|
|
96
129
|
private
|
|
97
130
|
|
|
131
|
+
# ADR-119 C1b / C2-c, #1570 / #1607 — the nearest level is asked of {Inference::DefinerResolution}, on the
|
|
132
|
+
# receiver's own side: the chain stands only where every candidate level gives one envelope in every
|
|
133
|
+
# world Ruby may have run (a fork, an unproven mixin order, a `possible`-only definer, and on the
|
|
134
|
+
# singleton side a hook that could have put a definer ahead). Where it does not, the answer is NO
|
|
135
|
+
# envelope and `@unknown` — the rule stays silent — instead of master's order, which is what #1570 and
|
|
136
|
+
# #1607 fired on. The levels the walk keeps for {#authoritative?} are the chain's.
|
|
98
137
|
def walk_to_owner(class_name)
|
|
99
138
|
@levels = []
|
|
100
139
|
@passed = []
|
|
101
|
-
|
|
102
|
-
|
|
140
|
+
@recorded = false
|
|
141
|
+
|
|
142
|
+
case Inference::DefinerResolution.resolve(@scope, class_name, @method_name, side,
|
|
143
|
+
question: :arity) { |chain, from| arity_hit(chain, from) }
|
|
144
|
+
in Inference::DefinerResolution::Known(answer: _answer, owner: _owner) then nearest_envelope(class_name)
|
|
145
|
+
in Inference::DefinerResolution::ABSENT then nearest_envelope(class_name) # rubocop:disable Lint/DuplicateBranch
|
|
146
|
+
in Inference::DefinerResolution::UNKNOWN then decline_unless_recorded_nowhere(class_name)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# A declined read of a name no project level records (`arity_hit` found no level that does, in any world it
|
|
151
|
+
# was asked) answers no envelope whether or not it stands, so the walk's reads are not the verdict's
|
|
152
|
+
# dependency: only a `def` appearing can change it, which {#settle_by_definitions} files, as for an
|
|
153
|
+
# `ABSENT` read. It can leave a stale false negative in a warm run, never a firing. Without this, every
|
|
154
|
+
# `Widget.new` (the implicit `Class` tail may answer it) would replay the whole walk as a file-level
|
|
155
|
+
# class edge.
|
|
156
|
+
def decline_unless_recorded_nowhere(class_name)
|
|
157
|
+
nowhere = !@recorded && !arity_chain(class_name).truncated?
|
|
158
|
+
nowhere ? nearest_envelope(class_name) : decline
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# The chain's nearest level's envelope. `Known#answer` is that envelope, but {#authoritative?} and
|
|
162
|
+
# {#settle_by_definitions} also read the levels, passed names and owner entries this walk fills, so the
|
|
163
|
+
# answer is not used on its own. Run only once the candidate-set read has answered, so the chain stands
|
|
164
|
+
# for the name.
|
|
165
|
+
def nearest_envelope(class_name) = owner_in(chain_levels(arity_chain(class_name)).first)
|
|
166
|
+
|
|
167
|
+
def decline
|
|
168
|
+
@levels = []
|
|
169
|
+
@passed = []
|
|
170
|
+
@owner_entries = []
|
|
171
|
+
@envelope = nil
|
|
172
|
+
@unknown = true
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# The first level at or after entry position `from` whose records name the method: its envelope (or
|
|
177
|
+
# {AMBIGUOUS}), the entry that recorded it and that entry's position and side on the chain. On the
|
|
178
|
+
# singleton side the class's own record is a `:singleton` entry and an extended module's an `:instance`
|
|
179
|
+
# one (`owner_entries` names the kind of each).
|
|
180
|
+
def arity_hit(chain, from)
|
|
181
|
+
start = 0
|
|
182
|
+
chain.levels.each_with_index do |raw, index|
|
|
183
|
+
level = Level.new(*raw)
|
|
184
|
+
entries = chain.level_entries(index)
|
|
185
|
+
first = start
|
|
186
|
+
start += entries.size
|
|
187
|
+
next if first < from
|
|
188
|
+
|
|
189
|
+
found = level_envelopes(level)
|
|
190
|
+
next if found.empty?
|
|
191
|
+
|
|
192
|
+
envelope = found.first
|
|
193
|
+
answer = found.all?(envelope) && !Source::ParameterEnvelope.opaque?(envelope) ? envelope : AMBIGUOUS
|
|
194
|
+
owner, owner_kind = owner_entries(level).first
|
|
195
|
+
position = first + (entries.index { |entry| entry.name == owner && entry.side == owner_kind } || 0)
|
|
196
|
+
@recorded = true
|
|
197
|
+
return ArityHit.new(answer, owner, position, owner_kind)
|
|
198
|
+
end
|
|
199
|
+
nil
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def owner_in(levels)
|
|
203
|
+
levels.each do |level|
|
|
103
204
|
@levels << level
|
|
104
205
|
found = level_envelopes(level)
|
|
105
206
|
if found.empty?
|
|
106
|
-
@passed.concat([
|
|
207
|
+
@passed.concat([level.class_name] + level.modules)
|
|
107
208
|
next
|
|
108
209
|
end
|
|
109
210
|
|
|
110
211
|
envelope = found.first
|
|
111
|
-
@owner_entries
|
|
212
|
+
@owner_entries |= owner_entries(level)
|
|
112
213
|
unless found.all?(envelope) && !Source::ParameterEnvelope.opaque?(envelope)
|
|
113
214
|
@ambiguous = true
|
|
114
215
|
return nil
|
|
@@ -155,56 +256,30 @@ module Rigor
|
|
|
155
256
|
@scope.discovered_classes.key?(name) || Rigor::Reflection.rbs_class_known?(name, scope: @scope)
|
|
156
257
|
end
|
|
157
258
|
|
|
158
|
-
# False when the walk stopped at the ADR-41 budget rather than
|
|
159
|
-
# exhaustion is uncertainty, and every caller reads it as a reason to decline.
|
|
160
|
-
def walked_whole_chain?(class_name)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return false if seen.size >= Scope::ANCESTOR_WALK_LIMIT
|
|
165
|
-
|
|
166
|
-
seen[current] = true
|
|
167
|
-
yield current
|
|
168
|
-
current = resolve(current, @scope.superclass_of(current))
|
|
169
|
-
end
|
|
170
|
-
true
|
|
259
|
+
# Yields each level of `class_name`'s chain. False when the walk stopped at the ADR-41 budget rather than
|
|
260
|
+
# ending: budget exhaustion is uncertainty, and every caller reads it as a reason to decline.
|
|
261
|
+
def walked_whole_chain?(class_name, &)
|
|
262
|
+
levels, whole = chain_levels(arity_chain(class_name))
|
|
263
|
+
levels.each(&)
|
|
264
|
+
whole
|
|
171
265
|
end
|
|
172
266
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
Level.new(class_name: class_name, modules: modules, externals: externals)
|
|
178
|
-
end
|
|
267
|
+
# `[levels, whole]` for one world of the chain, up to the first superclass the project does not declare.
|
|
268
|
+
def chain_levels(chain) = [chain.levels.map { |level| Level.new(*level) }, !chain.truncated?]
|
|
269
|
+
|
|
270
|
+
def side = @kind == :singleton ? :singleton : :instance
|
|
179
271
|
|
|
180
272
|
# Instance methods reach a receiver through `include` / `prepend`; class methods through `extend`, and
|
|
181
|
-
# an extended module's own `include`s reach the same singleton.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
|
|
191
|
-
# unambiguous mixins are untouched. Letting the decline arrive as an unknown EXTERNAL mixin instead
|
|
192
|
-
# suppressed the rule for every level of the receiver.
|
|
193
|
-
def collect_mixins(owner, raw_names, modules, externals, seen)
|
|
194
|
-
raw_names.each do |raw|
|
|
195
|
-
resolved = resolve(owner, raw)
|
|
196
|
-
names = resolved ? [resolved] : @scope.ambiguous_ancestor_resolutions(owner, raw)
|
|
197
|
-
next externals << @scope.ancestor_name_candidates(owner, raw) if names.empty?
|
|
198
|
-
|
|
199
|
-
names.each do |name|
|
|
200
|
-
next if seen[name]
|
|
201
|
-
|
|
202
|
-
seen[name] = true
|
|
203
|
-
modules << name
|
|
204
|
-
collect_mixins(name, @scope.includes_of(name), modules, externals, seen)
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
end
|
|
273
|
+
# an extended module's own `include`s reach the same singleton — the chain's singleton side.
|
|
274
|
+
#
|
|
275
|
+
# Issue #986 — the `:arity` flavor expands a name the compact-header rename collision left ambiguous
|
|
276
|
+
# to BOTH classes it names: both are ancestors at runtime and only their MRO order is unknowable. Taking
|
|
277
|
+
# both into the level is what keeps this rule alive for the rest of the receiver: a method only one of
|
|
278
|
+
# them declares still has one envelope and still fires, a method they disagree about lands two on the
|
|
279
|
+
# level's join and declines there, and the class's own `def`s, its superclass's and its unambiguous
|
|
280
|
+
# mixins are untouched. Letting the decline arrive as an unknown EXTERNAL mixin instead suppressed the
|
|
281
|
+
# rule for every level of the receiver.
|
|
282
|
+
def arity_chain(class_name) = Scope::ResolutionChain.for(@scope, class_name.to_s, side, :arity)
|
|
208
283
|
|
|
209
284
|
def resolve(owner, raw)
|
|
210
285
|
return nil if raw.nil?
|
|
@@ -215,8 +290,11 @@ module Rigor
|
|
|
215
290
|
bucket[raw] = @scope.ancestor_name_candidates(owner, raw).find { |name| project_class?(name) }
|
|
216
291
|
end
|
|
217
292
|
|
|
293
|
+
# A bucket of class marks alone (issue #1820: `x.singleton_class.prepend(Comparable)`) does not make a name
|
|
294
|
+
# the project's.
|
|
218
295
|
def project_class?(name)
|
|
219
|
-
@scope.known_user_class?(name) ||
|
|
296
|
+
@scope.known_user_class?(name) ||
|
|
297
|
+
Scope::DiscoveryIndex.declares_surface?(@scope.discovered_parameter_envelopes[name])
|
|
220
298
|
end
|
|
221
299
|
|
|
222
300
|
# A module's methods reach the receiver as instance methods whichever side the receiver is on.
|
|
@@ -231,8 +309,13 @@ module Rigor
|
|
|
231
309
|
level.externals.all? { |candidates| external_mixin_lacks_method?(candidates) }
|
|
232
310
|
end
|
|
233
311
|
|
|
312
|
+
# Issue #1797 — a class whose body rewrites its method table in a way no literal names. A `refine` of the
|
|
313
|
+
# class (`ENVELOPE_REFINED_MARK`) is not one here: whether a refinement answers a call is decided per call
|
|
314
|
+
# site, name and `using` by `CheckRules#refined_redefinition_in_effect?`, which also declines where a
|
|
315
|
+
# refinement of a subclass of the receiver's class may answer. Counting the mark silenced every name on
|
|
316
|
+
# every subclass of a refined class, with or without a `using`.
|
|
234
317
|
def dynamic_surface?(class_name)
|
|
235
|
-
|
|
318
|
+
@scope.parameter_envelopes_of(class_name).key?(Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK)
|
|
236
319
|
end
|
|
237
320
|
|
|
238
321
|
def project_patched?(class_name)
|
|
@@ -273,8 +356,8 @@ module Rigor
|
|
|
273
356
|
# A hook anywhere in the chain, including above the owner, and on either side: a class that answers
|
|
274
357
|
# names it does not define is a class whose `def`s are not the whole story of what a call reaches.
|
|
275
358
|
def chain_free_of_hooks?(class_name)
|
|
276
|
-
walked_whole_chain?(class_name) do |
|
|
277
|
-
([
|
|
359
|
+
walked_whole_chain?(class_name) do |level|
|
|
360
|
+
([level.class_name] + level.modules).each do |name|
|
|
278
361
|
envelopes = @scope.parameter_envelopes_of(name)
|
|
279
362
|
return false if dynamic_surface?(name)
|
|
280
363
|
return false if MISSING_HOOKS.any? { |hook| hook_recorded?(envelopes, hook) }
|
|
@@ -290,15 +373,48 @@ module Rigor
|
|
|
290
373
|
# receiver may be any of them, and each one's own definition of the name is what runs for it.
|
|
291
374
|
def subclasses_agree?(class_name, envelope)
|
|
292
375
|
each_subclass(class_name) do |subclass|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
376
|
+
subclass_levels(subclass).each do |level|
|
|
377
|
+
# A subclass whose own level the budget cut cannot be read, which is a reason to decline.
|
|
378
|
+
return false if level.nil?
|
|
379
|
+
return false unless clean_level?(level)
|
|
380
|
+
return false unless level_envelopes(level).all?(envelope)
|
|
381
|
+
end
|
|
296
382
|
end
|
|
297
383
|
true
|
|
298
384
|
end
|
|
299
385
|
|
|
386
|
+
# A subclass's own level (nil where the budget cut it or the chain does not stand for the name). It asks
|
|
387
|
+
# {Inference::DefinerResolution} on the receiver's side (ADR-119 C1b, C2-c): the subclass's own level
|
|
388
|
+
# answers, and a fork, an unproven mixin order, a `possible`-only definer or a hook there is `UNKNOWN`,
|
|
389
|
+
# which `subclasses_agree?` reads as a reason to decline.
|
|
390
|
+
def subclass_levels(subclass)
|
|
391
|
+
case Inference::DefinerResolution.resolve(@scope, subclass, @method_name, side, question: :arity,
|
|
392
|
+
&own_level_answer(subclass))
|
|
393
|
+
in Inference::DefinerResolution::Known(answer: _answer, owner: _owner) then [first_level(subclass)]
|
|
394
|
+
in Inference::DefinerResolution::ABSENT then [nil]
|
|
395
|
+
in Inference::DefinerResolution::UNKNOWN then NO_LEVELS
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# The subclass's own level, always a candidate (an empty answer where it records nothing), so that the
|
|
400
|
+
# read asks whether the chain stands for the subclass and never whether an ancestor defines the name.
|
|
401
|
+
def own_level_answer(subclass)
|
|
402
|
+
lambda do |chain, from|
|
|
403
|
+
next nil unless from.zero? && !chain.levels.empty?
|
|
404
|
+
|
|
405
|
+
level = Level.new(*chain.levels.first)
|
|
406
|
+
ArityHit.new(level_envelopes(level).uniq, subclass, 0, side)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def first_level(class_name) = chain_levels(arity_chain(class_name)).first.first
|
|
411
|
+
|
|
412
|
+
# ADR-46 — every class whose subclasses are enumerated records `subclass_of:<last segment>`, which a class
|
|
413
|
+
# declaration with that superclass appearing or vanishing fires (issue #1820): a new subclass with a
|
|
414
|
+
# different envelope re-checks the call.
|
|
300
415
|
def each_subclass(class_name)
|
|
301
416
|
children = children_by_parent
|
|
417
|
+
record_subclasses_read(class_name)
|
|
302
418
|
queue = children.fetch(class_name.to_s, []).dup
|
|
303
419
|
seen = {}
|
|
304
420
|
until queue.empty?
|
|
@@ -306,11 +422,18 @@ module Rigor
|
|
|
306
422
|
next if seen[subclass]
|
|
307
423
|
|
|
308
424
|
seen[subclass] = true
|
|
425
|
+
record_subclasses_read(subclass)
|
|
309
426
|
yield subclass
|
|
310
427
|
queue.concat(children.fetch(subclass, []))
|
|
311
428
|
end
|
|
312
429
|
end
|
|
313
430
|
|
|
431
|
+
def record_subclasses_read(class_name)
|
|
432
|
+
return unless Analysis::DependencyRecorder.active?
|
|
433
|
+
|
|
434
|
+
Analysis::DependencyRecorder.read_last_segment(:subclass_of, class_name.to_s)
|
|
435
|
+
end
|
|
436
|
+
|
|
314
437
|
def children_by_parent
|
|
315
438
|
@scope.discovered_superclasses.each_with_object({}) do |(child, raw_parent), children|
|
|
316
439
|
parent = resolve(child, raw_parent)
|