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.
Files changed (237) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/data/builtins/ruby_core/io.yml +1 -0
  4. data/data/builtins/ruby_core/proc.yml +4 -0
  5. data/data/builtins/ruby_core/random.yml +4 -0
  6. data/data/builtins/ruby_core/struct.yml +3 -0
  7. data/data/core_overlay/env.rbs +34 -0
  8. data/data/core_overlay/gc.rbs +50 -0
  9. data/data/core_overlay/hash.rbs +45 -0
  10. data/data/core_overlay/integer.rbs +31 -0
  11. data/data/core_overlay/io_buffer.rbs +27 -0
  12. data/data/core_overlay/kernel.rbs +40 -0
  13. data/data/core_overlay/match_data.rbs +28 -0
  14. data/data/core_overlay/module.rbs +51 -0
  15. data/data/core_overlay/object_space.rbs +29 -0
  16. data/data/core_overlay/proc.rbs +45 -0
  17. data/data/core_overlay/range.rbs +40 -0
  18. data/data/core_overlay/source_range.rbs +78 -0
  19. data/data/core_overlay/string.rbs +86 -0
  20. data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
  21. data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
  22. data/docs/handbook/01-getting-started.md +3 -3
  23. data/docs/handbook/06-classes.md +3 -1
  24. data/docs/handbook/08-understanding-errors.md +3 -2
  25. data/docs/handbook/11-sig-gen.md +6 -0
  26. data/docs/handbook/appendix-liskov.md +5 -0
  27. data/docs/install.md +87 -15
  28. data/docs/manual/02-cli-reference.md +2 -2
  29. data/docs/manual/03-configuration.md +6 -1
  30. data/docs/manual/04-diagnostics.md +2 -1
  31. data/docs/manual/06-baseline.md +2 -0
  32. data/docs/manual/07-plugins.md +1 -1
  33. data/docs/manual/08-skills.md +4 -3
  34. data/docs/manual/12-caching.md +43 -4
  35. data/docs/manual/14-rails-quickstart.md +19 -11
  36. data/docs/manual/18-removing-dead-code.md +32 -2
  37. data/docs/manual/plugins/README.md +6 -1
  38. data/docs/manual/plugins/rigor-activerecord.md +21 -0
  39. data/docs/manual/plugins/rigor-alba.md +60 -0
  40. data/docs/manual/plugins/rigor-minitest.md +22 -4
  41. data/docs/manual/plugins/rigor-typelizer.md +71 -0
  42. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
  43. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
  44. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
  45. data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
  47. data/lib/rigor/analysis/check_rules.rb +407 -144
  48. data/lib/rigor/analysis/dependency_recorder.rb +215 -18
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
  52. data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
  53. data/lib/rigor/analysis/erb_template_detector.rb +2 -1
  54. data/lib/rigor/analysis/incremental.rb +29 -9
  55. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
  56. data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
  57. data/lib/rigor/analysis/incremental_session.rb +287 -50
  58. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
  59. data/lib/rigor/analysis/reachability/graph.rb +123 -23
  60. data/lib/rigor/analysis/reachability/scan.rb +290 -36
  61. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
  62. data/lib/rigor/analysis/rule_catalog.rb +43 -0
  63. data/lib/rigor/analysis/run_cache_key.rb +43 -9
  64. data/lib/rigor/analysis/run_stats.rb +2 -2
  65. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
  68. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  69. data/lib/rigor/analysis/runner.rb +371 -49
  70. data/lib/rigor/analysis/shadow_harness.rb +27 -0
  71. data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
  72. data/lib/rigor/analysis/template_unit_paths.rb +6 -4
  73. data/lib/rigor/analysis/template_units.rb +15 -5
  74. data/lib/rigor/analysis/worker_session.rb +11 -7
  75. data/lib/rigor/cache/descriptor.rb +40 -1
  76. data/lib/rigor/cache/file_digest.rb +28 -0
  77. data/lib/rigor/cache/incremental_snapshot.rb +92 -8
  78. data/lib/rigor/cache/store.rb +63 -3
  79. data/lib/rigor/cache/value_digest.rb +325 -0
  80. data/lib/rigor/cli/annotate_command.rb +5 -0
  81. data/lib/rigor/cli/baseline_command.rb +7 -18
  82. data/lib/rigor/cli/check_command.rb +85 -14
  83. data/lib/rigor/cli/doctor_command.rb +1 -1
  84. data/lib/rigor/cli/effects_report.rb +2 -2
  85. data/lib/rigor/cli/trace_command.rb +5 -0
  86. data/lib/rigor/cli/type_of_command.rb +5 -3
  87. data/lib/rigor/cli/unused_command.rb +16 -10
  88. data/lib/rigor/cli.rb +11 -3
  89. data/lib/rigor/config_audit.rb +3 -2
  90. data/lib/rigor/config_path.rb +29 -0
  91. data/lib/rigor/configuration/severity_profile.rb +11 -0
  92. data/lib/rigor/configuration.rb +52 -7
  93. data/lib/rigor/effects/plugin_facts.rb +6 -6
  94. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
  95. data/lib/rigor/environment/gated_signature_guard.rb +43 -0
  96. data/lib/rigor/environment/gated_signature_plan.rb +339 -0
  97. data/lib/rigor/environment/lockfile_resolver.rb +10 -1
  98. data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
  99. data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
  100. data/lib/rigor/environment/rbs_loader.rb +284 -15
  101. data/lib/rigor/environment/required_features.rb +206 -0
  102. data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
  103. data/lib/rigor/environment.rb +34 -73
  104. data/lib/rigor/inference/acceptance.rb +28 -3
  105. data/lib/rigor/inference/block_parameter_binder.rb +29 -13
  106. data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
  107. data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
  108. data/lib/rigor/inference/captured_locals.rb +7 -1
  109. data/lib/rigor/inference/declaration_walk/context.rb +195 -0
  110. data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
  111. data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
  112. data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
  113. data/lib/rigor/inference/declaration_walk.rb +7 -0
  114. data/lib/rigor/inference/def_node_resolver.rb +26 -3
  115. data/lib/rigor/inference/definer_resolution.rb +266 -0
  116. data/lib/rigor/inference/expression_typer.rb +351 -61
  117. data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
  118. data/lib/rigor/inference/global_write_census.rb +7 -4
  119. data/lib/rigor/inference/guard_rebinding.rb +54 -13
  120. data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
  121. data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
  122. data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
  123. data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
  124. data/lib/rigor/inference/in_effect_refinements.rb +256 -0
  125. data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
  126. data/lib/rigor/inference/key_presence_guard.rb +303 -0
  127. data/lib/rigor/inference/macro_block_self_type.rb +73 -14
  128. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  129. data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
  130. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
  131. data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
  132. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
  133. data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
  134. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
  135. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
  136. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
  137. data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
  138. data/lib/rigor/inference/method_dispatcher.rb +44 -11
  139. data/lib/rigor/inference/module_function_state.rb +214 -0
  140. data/lib/rigor/inference/multi_target_binder.rb +21 -16
  141. data/lib/rigor/inference/narrowing.rb +166 -22
  142. data/lib/rigor/inference/object_mixins.rb +180 -0
  143. data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
  144. data/lib/rigor/inference/project_patched_methods.rb +10 -5
  145. data/lib/rigor/inference/project_patched_scanner.rb +71 -3
  146. data/lib/rigor/inference/refine_census.rb +239 -0
  147. data/lib/rigor/inference/refine_self.rb +317 -0
  148. data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
  149. data/lib/rigor/inference/refined_dispatch.rb +272 -0
  150. data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
  151. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
  152. data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
  153. data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
  154. data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
  155. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
  156. data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
  157. data/lib/rigor/inference/scope_indexer.rb +2129 -354
  158. data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
  159. data/lib/rigor/inference/statement_evaluator.rb +149 -32
  160. data/lib/rigor/inference/string_mutation.rb +9 -3
  161. data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
  162. data/lib/rigor/inference/version_guard.rb +19 -10
  163. data/lib/rigor/language_server/project_context.rb +31 -0
  164. data/lib/rigor/language_server/server.rb +18 -0
  165. data/lib/rigor/plugin/base.rb +28 -8
  166. data/lib/rigor/plugin/inflector.rb +1 -1
  167. data/lib/rigor/plugin/io_boundary.rb +124 -7
  168. data/lib/rigor/plugin/isolation.rb +1 -1
  169. data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
  170. data/lib/rigor/plugin/trust_policy.rb +3 -3
  171. data/lib/rigor/project_environment.rb +29 -6
  172. data/lib/rigor/protection/discovery_seed.rb +18 -9
  173. data/lib/rigor/protection/mutation_cache.rb +4 -4
  174. data/lib/rigor/protection/mutator.rb +1 -1
  175. data/lib/rigor/reflection/constant_ancestors.rb +211 -46
  176. data/lib/rigor/reflection/constant_path.rb +33 -7
  177. data/lib/rigor/reflection.rb +41 -10
  178. data/lib/rigor/scope/discovery_index.rb +258 -3
  179. data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
  180. data/lib/rigor/scope/resolution_chain.rb +1032 -0
  181. data/lib/rigor/scope.rb +480 -328
  182. data/lib/rigor/sig_gen/alias_index.rb +1 -1
  183. data/lib/rigor/sig_gen/classification.rb +4 -1
  184. data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
  185. data/lib/rigor/sig_gen/generator.rb +77 -21
  186. data/lib/rigor/sig_gen/method_candidate.rb +2 -2
  187. data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
  188. data/lib/rigor/source/alias_names.rb +52 -0
  189. data/lib/rigor/source/literals.rb +3 -2
  190. data/lib/rigor/source/symbol_name.rb +20 -0
  191. data/lib/rigor/type/combinator.rb +59 -3
  192. data/lib/rigor/type/float_range.rb +4 -1
  193. data/lib/rigor/type/nominal.rb +6 -1
  194. data/lib/rigor/version.rb +1 -1
  195. data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
  196. data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
  197. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
  198. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
  199. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
  200. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
  201. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
  202. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
  203. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
  204. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
  205. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
  206. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
  207. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
  208. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
  209. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
  210. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
  211. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
  212. data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
  213. data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
  214. data/plugins/rigor-alba/sig/alba.rbs +4 -0
  215. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
  216. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
  217. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
  218. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
  219. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
  220. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
  221. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
  222. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
  223. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
  224. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
  225. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
  226. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
  227. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
  228. data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
  229. data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
  230. data/sig/rigor/environment.rbs +2 -0
  231. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  232. data/sig/rigor/scope.rbs +69 -4
  233. data/sig/rigor/source.rbs +4 -0
  234. data/sig/rigor.rbs +3 -1
  235. data/skills/rigor-project-init/SKILL.md +37 -11
  236. data/skills/rigor-project-init/references/02-configure.md +7 -2
  237. metadata +90 -1
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Inference
5
+ # ADR-119 WD2 — the candidate-set read over {Scope::ResolutionChain}, for a reader that can decline. It is
6
+ # internal (not a `Scope` method, so the pinned public surface is unchanged) and answers one of three
7
+ # things about a method name on a class:
8
+ #
9
+ # - {Known} — every candidate gives the same answer to the question asked;
10
+ # - {UNKNOWN} — the chain does not stand for this name (a mark not discharged, a fork, a retro world that
11
+ # answers otherwise), a candidate disagrees, the walk was cut by the budget, an external ancestor ahead of
12
+ # the candidate may answer first (one RBS does not know, or knows and declares the name in; one it knows
13
+ # and whose declaration lacks the name is skipped);
14
+ # - {ABSENT} — no project entry, no external entry and not the implicit `Object` can answer.
15
+ #
16
+ # On the singleton side (ADR-119 WD3, errata 2026-10-08) the chain is `K`'s singleton, its extends' instance
17
+ # chains, then the superclass's singleton chain; an external superclass entry stands for its whole tail and is
18
+ # tested against RBS's singleton side, an extended external module against its instance side, the implicit tail
19
+ # after a project superclass against `Object`'s singleton side. A standing chain is then read past
20
+ # {SingletonHookDecline}' positional decline: a hook's singleton-side edge is recorded on no includer, so the read
21
+ # declines where one could have landed ahead of its last candidate.
22
+ #
23
+ # THE CALL SITE IS A CONTRACT. A result is consumed only by an exhaustive `case/in` that is the call's own
24
+ # direct predicate, with one arm per answer and no `else` or `in _`, and is never stored, returned or
25
+ # truth-tested: {UNKNOWN} and {ABSENT} are both truthy, so any other use folds a decline into a firing arm.
26
+ # `spec/rigor/inference/definer_resolution_case_in_spec.rb` fails on a call site of any other shape.
27
+ #
28
+ # It migrates one question at a time: `SourceArity` (C1b) and the relationship lints (C1c) read the instance
29
+ # side; no production reader reads the singleton side yet (C2-a landed it for C2's typing sites).
30
+ module DefinerResolution
31
+ Known = Data.define(:answer, :owner)
32
+ UnknownResult = Data.define
33
+ AbsentResult = Data.define
34
+ UNKNOWN = UnknownResult.new
35
+ ABSENT = AbsentResult.new
36
+
37
+ # One candidate definer as a per-chain answer function reports it: the `answer` to the question, the
38
+ # `owner` entry's name, the `index` of that entry on the chain and its `side` (`:instance` or `:singleton`).
39
+ Hit = Data.define(:answer, :owner, :index, :side)
40
+
41
+ QUESTIONS = %i[definer visibility arity override own_side].freeze
42
+ NONE = :rigor_definer_resolution_none
43
+ STRAY = :rigor_definer_resolution_stray
44
+ private_constant :NONE, :STRAY
45
+
46
+ module_function
47
+
48
+ # `answer_in` is the question's per-chain answer function: called with a chain and a position, it returns
49
+ # the {Hit} of the first project entry at or after the position that defines `method_name`, or nil. `:definer`
50
+ # and `:visibility` have a default; every other question supplies its own. It is called once per candidate
51
+ # on the chain, and again on the retro world when the chain has one fork, where `from` is mapped to the
52
+ # entry it followed.
53
+ def resolve(scope, class_name, method_name, side, question:, from: 0, &answer_in)
54
+ raise ArgumentError, "unknown question #{question.inspect}" unless QUESTIONS.include?(question)
55
+
56
+ answer_in = override_answer(scope, &answer_in) if question == :override
57
+ answer_in ||= default_answer(scope, method_name, question)
58
+ flavor = question == :arity ? :arity : :methods
59
+ chain = Scope::ResolutionChain.for(scope, class_name.to_s, side, flavor)
60
+
61
+ hits = candidates(scope, chain, method_name, from, answer_in)
62
+ return UNKNOWN if hits.nil?
63
+
64
+ verdict = chain.settle(scope, outcomes(hits), owner: hits.first&.owner, unknown_for: method_name) do |retro|
65
+ retro_from = retro_position(chain, retro, from)
66
+ outcomes(retro_from && candidates(scope, retro, method_name, retro_from, answer_in))
67
+ end
68
+ return UNKNOWN unless verdict == :chain
69
+ return UNKNOWN if side == :singleton && SingletonHookDecline.decline?(scope, chain, method_name, from, hits)
70
+
71
+ collapse(hits)
72
+ end
73
+
74
+ # The `:override` question's answer function, built from the caller's block: that block is NOT a per-chain
75
+ # function but `|owner_name| value`, called on each project entry in order, and its first non-nil `value` is
76
+ # the entry's answer, as `[owner_name, value]` (the owner is part of the answer so that two worlds naming
77
+ # different parents disagree). It is the override lints' parent read: the nearest project ancestor after the
78
+ # class whose block answers.
79
+ def override_answer(scope)
80
+ lambda do |chain, from|
81
+ first_hit(chain, scope, from) do |entry|
82
+ value = yield(entry.name)
83
+ [entry.name, value] unless value.nil?
84
+ end
85
+ end
86
+ end
87
+
88
+ # `from` is a position on `chain`; the same position on the retro world is just after the entry it followed.
89
+ # An entry the chain carries twice cannot be matched, which declines.
90
+ def retro_position(chain, retro, from)
91
+ return 0 if from.zero?
92
+
93
+ entry = chain.entries[from - 1]
94
+ return nil if entry.nil? || chain.entries.count(entry) > 1
95
+
96
+ found = retro.entries.index(entry)
97
+ found && (found + 1)
98
+ end
99
+
100
+ # The first definer and, while that definer is `possible`, the next; a nil joins the set when nothing
101
+ # follows (the absent candidate). A cut chain, and a chain on which an external ancestor ahead of the
102
+ # candidate (or, with no candidate, the implicit `Object`) may define the name, is a decline, answered by
103
+ # nil instead of a set.
104
+ def candidates(scope, chain, method_name, from, answer_in)
105
+ hits = []
106
+ position = from
107
+ loop do
108
+ hit = answer_in.call(chain, position)
109
+ hit = past_fold_copies(scope, chain, method_name, hit, answer_in) if chain.side == :singleton
110
+ return nil if hit.equal?(STRAY)
111
+
112
+ raise ArgumentError, "answer function went backwards" if hit && hit.index < position
113
+ return nil if hit.nil? && chain.truncated?
114
+
115
+ hits << hit
116
+ return decided(scope, chain, method_name, from, hits) if hit.nil? || !possible?(scope, hit, method_name)
117
+
118
+ position = hit.index + 1
119
+ end
120
+ end
121
+
122
+ # A class entry answering through a copy the extends fold made is asked past
123
+ # (`SingletonHookDecline.copy_kind`), so the module that wrote the `def` answers at its own position; a copy
124
+ # from a module the chain does not hold on that level is {STRAY}, a decline.
125
+ def past_fold_copies(scope, chain, method_name, hit, answer_in)
126
+ while hit
127
+ case SingletonHookDecline.copy_kind(scope, chain, method_name, hit)
128
+ when :copy then hit = answer_in.call(chain, hit.index + 1)
129
+ when :stray then return STRAY
130
+ else return hit
131
+ end
132
+ end
133
+ hit
134
+ end
135
+
136
+ def decided(scope, chain, method_name, from, hits)
137
+ external_may_answer?(scope, chain, method_name, from, hits.last) ? nil : hits
138
+ end
139
+
140
+ # An external entry ahead of the last candidate that RBS does not know, or knows and declares the name in,
141
+ # may be the definer Ruby calls; with no candidate, so may the implicit `Object` (`Kernel`, for the object
142
+ # that every class is). Both decline. Each tested external files the negative class edge on its spelling.
143
+ def external_may_answer?(scope, chain, method_name, from, last)
144
+ stop = last.nil? ? chain.entries.size : last.index
145
+ recording = Analysis::DependencyRecorder.active?
146
+ (from...stop).any? do |index|
147
+ entry = chain.entries[index]
148
+ next false unless entry.external?
149
+
150
+ Analysis::DependencyRecorder.read_missing(:class, entry.raw.to_s.split("::").last) if recording
151
+ !external_lacks?(scope, entry.candidates, entry.side, method_name)
152
+ end || (last.nil? && implicit_object_answers?(scope, chain, method_name))
153
+ end
154
+
155
+ # The implicit tail after a chain's last project superclass: `Object` (with `Kernel` and `BasicObject`) on the
156
+ # instance side, `#<Class:Object>` through `Class`, `Module` and `Kernel` on the singleton side.
157
+ def implicit_object_answers?(scope, chain, method_name)
158
+ !external_lacks?(scope, ["Object"], chain.side, method_name)
159
+ end
160
+
161
+ # True only for an ancestor RBS knows whose declaration lacks the name, on the side the entry stands for: an
162
+ # external superclass on a singleton chain (`:singleton`, its whole tail) is asked for a singleton method, an
163
+ # extended module (`:instance`) and every instance-side entry for an instance method.
164
+ def external_lacks?(scope, candidates, side, method_name)
165
+ return Scope::ResolutionChain::Relevance.external_lacks?(scope, candidates, method_name) if side == :instance
166
+
167
+ known = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: scope) }
168
+ return false if known.nil?
169
+
170
+ Rigor::Reflection.singleton_method_definition(known, method_name, scope: scope).nil?
171
+ rescue StandardError
172
+ false
173
+ end
174
+
175
+ # What a candidate set says, for {Scope::ResolutionChain#settle} to compare across worlds.
176
+ def outcomes(hits)
177
+ return NONE if hits.nil?
178
+
179
+ hits.map { |hit| hit.nil? ? NONE : hit.answer }
180
+ end
181
+
182
+ def collapse(hits)
183
+ first = hits.first
184
+ return ABSENT if hits.all?(&:nil?)
185
+ return UNKNOWN if hits.any?(&:nil?) || hits.any? { |hit| hit.answer != first.answer }
186
+
187
+ Known.new(first.answer, first.owner)
188
+ end
189
+
190
+ # A definer that rests on a fact the walk could not prove executes: the member's `possible` sibling, or a
191
+ # contested slot of one of the single-valued members.
192
+ def possible?(scope, hit, method_name)
193
+ discovery = scope.discovery
194
+ owner = hit.owner
195
+ kind = hit.side == :singleton ? :singleton : :instance
196
+ nodes = kind == :singleton ? :discovered_singleton_def_nodes : :discovered_def_nodes
197
+ discovery.possible_method?(owner, method_name, kind) ||
198
+ discovery.contested?(nodes, [owner, method_name.to_sym]) ||
199
+ discovery.contested?(:discovered_method_visibilities, [owner, method_name.to_sym]) ||
200
+ discovery.contested?(:discovered_parameter_envelopes, [owner, [kind, method_name.to_sym]])
201
+ end
202
+
203
+ def default_answer(scope, method_name, question)
204
+ case question
205
+ when :definer then definer_answer(scope, method_name)
206
+ when :visibility then visibility_answer(scope, method_name)
207
+ else raise ArgumentError, "question #{question.inspect} needs an answer function"
208
+ end
209
+ end
210
+
211
+ # The first project entry with a `def` of the name, as `[def node, owner]`.
212
+ def definer_answer(scope, method_name)
213
+ lambda do |chain, from|
214
+ first_hit(chain, scope, from) do |entry|
215
+ node = if entry.side == :singleton
216
+ scope.singleton_def_for(entry.name,
217
+ method_name)
218
+ else
219
+ scope.user_def_for(entry.name,
220
+ method_name)
221
+ end
222
+ [node, entry.name] if node
223
+ end
224
+ end
225
+ end
226
+
227
+ # The first project entry that records the name (a discovered method, an instance `def` the def table
228
+ # holds, or a visibility change such as `private :foo` recorded for it), with its visibility (`:public` where
229
+ # none was stated).
230
+ def visibility_answer(scope, method_name)
231
+ lambda do |chain, from|
232
+ first_hit(chain, scope, from) do |entry|
233
+ kind = entry.side == :singleton ? :singleton : :instance
234
+ next unless scope.discovered_method?(entry.name, method_name, kind) ||
235
+ (kind == :instance && (scope.user_def_for(entry.name, method_name) ||
236
+ scope.discovered_method_visibility(entry.name, method_name)))
237
+
238
+ scope.discovered_method_visibility(entry.name, method_name) || :public
239
+ end
240
+ end
241
+ end
242
+
243
+ def first_hit(chain, scope, from)
244
+ index = from - 1
245
+ found = nil
246
+ chain.search(scope, from) do |entry|
247
+ index += 1
248
+ next if entry.external?
249
+
250
+ answer = yield(entry)
251
+ next if answer.nil?
252
+
253
+ found = Hit.new(answer, entry.name, index, entry.side)
254
+ end
255
+ found
256
+ end
257
+
258
+ private_class_method :override_answer, :retro_position, :candidates, :past_fold_copies, :decided,
259
+ :external_may_answer?, :implicit_object_answers?, :external_lacks?,
260
+ :outcomes, :collapse, :possible?,
261
+ :default_answer, :definer_answer, :visibility_answer, :first_hit
262
+ end
263
+ end
264
+ end
265
+
266
+ require_relative "singleton_hook_decline"