rigortype 0.4.1 → 0.4.3

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 (230) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  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/11-sig-gen.md +18 -5
  25. data/docs/handbook/appendix-liskov.md +5 -0
  26. data/docs/manual/02-cli-reference.md +19 -1
  27. data/docs/manual/03-configuration.md +7 -5
  28. data/docs/manual/04-diagnostics.md +2 -1
  29. data/docs/manual/05-inspecting-types.md +50 -0
  30. data/docs/manual/06-baseline.md +2 -0
  31. data/docs/manual/07-plugins.md +1 -1
  32. data/docs/manual/12-caching.md +18 -1
  33. data/docs/manual/14-rails-quickstart.md +2 -2
  34. data/docs/manual/18-removing-dead-code.md +41 -4
  35. data/docs/manual/plugins/README.md +6 -1
  36. data/docs/manual/plugins/rigor-activerecord.md +21 -0
  37. data/docs/manual/plugins/rigor-alba.md +60 -0
  38. data/docs/manual/plugins/rigor-minitest.md +22 -4
  39. data/docs/manual/plugins/rigor-typelizer.md +71 -0
  40. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
  41. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
  42. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
  43. data/lib/rigor/analysis/check_rules/rule_ids.rb +11 -0
  44. data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
  45. data/lib/rigor/analysis/check_rules.rb +292 -59
  46. data/lib/rigor/analysis/dependency_recorder.rb +15 -0
  47. data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
  48. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
  49. data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
  50. data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
  51. data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
  52. data/lib/rigor/analysis/erb_template_detector.rb +2 -1
  53. data/lib/rigor/analysis/incremental.rb +29 -9
  54. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
  55. data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
  56. data/lib/rigor/analysis/incremental_session.rb +103 -23
  57. data/lib/rigor/analysis/path_expansion.rb +62 -6
  58. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
  59. data/lib/rigor/analysis/reachability/graph.rb +241 -37
  60. data/lib/rigor/analysis/reachability/scan.rb +490 -77
  61. data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
  62. data/lib/rigor/analysis/rule_catalog.rb +43 -0
  63. data/lib/rigor/analysis/run_cache_key.rb +16 -1
  64. data/lib/rigor/analysis/run_cache_probe.rb +1 -1
  65. data/lib/rigor/analysis/run_stats.rb +28 -6
  66. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
  67. data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
  68. data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  70. data/lib/rigor/analysis/runner.rb +62 -13
  71. data/lib/rigor/analysis/worker_session.rb +8 -5
  72. data/lib/rigor/cache/engine_source.rb +21 -7
  73. data/lib/rigor/cache/incremental_snapshot.rb +55 -5
  74. data/lib/rigor/cli/annotate_command.rb +5 -0
  75. data/lib/rigor/cli/baseline_command.rb +7 -18
  76. data/lib/rigor/cli/check_command.rb +140 -40
  77. data/lib/rigor/cli/docs_command.rb +5 -4
  78. data/lib/rigor/cli/lens_command.rb +144 -0
  79. data/lib/rigor/cli/lsp_command.rb +5 -2
  80. data/lib/rigor/cli/skill_command.rb +3 -3
  81. data/lib/rigor/cli/skill_describe.rb +1 -1
  82. data/lib/rigor/cli/trace_command.rb +5 -0
  83. data/lib/rigor/cli/type_of_command.rb +6 -3
  84. data/lib/rigor/cli/unused_command.rb +166 -27
  85. data/lib/rigor/cli.rb +39 -5
  86. data/lib/rigor/configuration/severity_profile.rb +6 -0
  87. data/lib/rigor/configuration.rb +61 -2
  88. data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
  89. data/lib/rigor/environment/gated_signature_guard.rb +43 -0
  90. data/lib/rigor/environment/gated_signature_plan.rb +339 -0
  91. data/lib/rigor/environment/installed_gem_set.rb +38 -11
  92. data/lib/rigor/environment/lockfile_resolver.rb +37 -6
  93. data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
  94. data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
  95. data/lib/rigor/environment/rbs_loader.rb +316 -14
  96. data/lib/rigor/environment/required_features.rb +208 -0
  97. data/lib/rigor/environment.rb +33 -8
  98. data/lib/rigor/inference/acceptance.rb +28 -3
  99. data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
  100. data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
  101. data/lib/rigor/inference/captured_locals.rb +43 -3
  102. data/lib/rigor/inference/def_node_resolver.rb +23 -1
  103. data/lib/rigor/inference/expression_typer.rb +261 -29
  104. data/lib/rigor/inference/global_write_census.rb +7 -4
  105. data/lib/rigor/inference/guard_rebinding.rb +1 -1
  106. data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
  107. data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
  108. data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +286 -0
  109. data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
  110. data/lib/rigor/inference/in_effect_refinements.rb +256 -0
  111. data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
  112. data/lib/rigor/inference/key_presence_guard.rb +303 -0
  113. data/lib/rigor/inference/macro_block_self_type.rb +59 -12
  114. data/lib/rigor/inference/match_rebinding.rb +1 -1
  115. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  116. data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
  117. data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
  118. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
  119. data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
  120. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
  121. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
  122. data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
  123. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
  124. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
  125. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
  126. data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
  127. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
  128. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
  129. data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
  130. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
  131. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
  132. data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
  133. data/lib/rigor/inference/method_dispatcher.rb +96 -14
  134. data/lib/rigor/inference/module_function_state.rb +2 -1
  135. data/lib/rigor/inference/narrowing.rb +20 -6
  136. data/lib/rigor/inference/object_mixins.rb +180 -0
  137. data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
  138. data/lib/rigor/inference/project_patched_methods.rb +12 -7
  139. data/lib/rigor/inference/project_patched_scanner.rb +108 -26
  140. data/lib/rigor/inference/refine_census.rb +239 -0
  141. data/lib/rigor/inference/refine_self.rb +317 -0
  142. data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
  143. data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
  144. data/lib/rigor/inference/refined_dispatch.rb +320 -0
  145. data/lib/rigor/inference/repeated_or_writes.rb +2 -0
  146. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
  147. data/lib/rigor/inference/scope_indexer.rb +827 -65
  148. data/lib/rigor/inference/statement_evaluator.rb +294 -51
  149. data/lib/rigor/inference/string_mutation.rb +9 -3
  150. data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
  151. data/lib/rigor/inference/untyped_rejoin.rb +75 -0
  152. data/lib/rigor/inference/version_guard.rb +19 -10
  153. data/lib/rigor/language_server/project_context.rb +87 -5
  154. data/lib/rigor/language_server/server.rb +18 -0
  155. data/lib/rigor/lens/anchor.rb +115 -0
  156. data/lib/rigor/lens/contract_reader.rb +118 -0
  157. data/lib/rigor/lens/document.rb +113 -0
  158. data/lib/rigor/lens/file_reader.rb +433 -0
  159. data/lib/rigor/lens/members.rb +153 -0
  160. data/lib/rigor/lens/parameters.rb +91 -0
  161. data/lib/rigor/lens/plugins.rb +40 -0
  162. data/lib/rigor/lens/run.rb +280 -0
  163. data/lib/rigor/lens/slot.rb +79 -0
  164. data/lib/rigor/lens.rb +40 -0
  165. data/lib/rigor/plugin/inflector.rb +1 -1
  166. data/lib/rigor/plugin/io_boundary.rb +42 -3
  167. data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
  168. data/lib/rigor/project_environment.rb +48 -6
  169. data/lib/rigor/protection/mutation_cache.rb +3 -2
  170. data/lib/rigor/protection/mutator.rb +1 -1
  171. data/lib/rigor/reflection/constant_ancestors.rb +127 -11
  172. data/lib/rigor/reflection/constant_path.rb +13 -2
  173. data/lib/rigor/reflection.rb +40 -8
  174. data/lib/rigor/scope/discovery_index.rb +40 -1
  175. data/lib/rigor/scope/resolution_chain.rb +159 -11
  176. data/lib/rigor/scope.rb +211 -21
  177. data/lib/rigor/sig_gen/classification.rb +4 -1
  178. data/lib/rigor/sig_gen/generator.rb +100 -10
  179. data/lib/rigor/sig_gen/method_candidate.rb +2 -2
  180. data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
  181. data/lib/rigor/source/alias_names.rb +52 -0
  182. data/lib/rigor/source/literals.rb +3 -2
  183. data/lib/rigor/source/symbol_name.rb +20 -0
  184. data/lib/rigor/type/combinator.rb +34 -2
  185. data/lib/rigor/type/float_range.rb +4 -1
  186. data/lib/rigor/type/nominal.rb +6 -1
  187. data/lib/rigor/version.rb +1 -1
  188. data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
  189. data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
  190. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
  191. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
  192. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
  193. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
  194. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
  195. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
  196. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
  197. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
  198. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
  199. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
  200. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
  201. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
  202. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
  203. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
  204. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
  205. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
  206. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
  207. data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
  208. data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
  209. data/plugins/rigor-alba/sig/alba.rbs +4 -0
  210. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
  211. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
  212. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
  213. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
  214. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
  215. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
  216. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
  217. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
  218. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
  219. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
  220. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
  221. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
  222. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
  223. data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
  224. data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
  225. data/sig/rigor/plugin/io_boundary.rbs +3 -0
  226. data/sig/rigor/scope.rbs +30 -4
  227. data/sig/rigor/source.rbs +4 -0
  228. data/sig/rigor.rbs +9 -4
  229. data/skills/rigor-project-init/references/02-configure.md +2 -2
  230. metadata +83 -1
@@ -0,0 +1,475 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../builtins/cruby_definers"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module RefinedDispatch
8
+ # Issue #1740 — does the receiver's own lookup answer a call ahead of every refinement in effect, so that
9
+ # `call.wrong-arity` and `call.argument-type-mismatch` may check it against the receiver's own method? Ruby
10
+ # finds a method the receiver's class (or a nearer ancestor) defines before it consults an ancestor's
11
+ # refinement: under `refine Object do def center(a, b, c)`, `"x".center(1, 2, 3)` still runs `String#center`
12
+ # and raises.
13
+ #
14
+ # It answers yes only from positive knowledge (the caller has already declined on an in-effect list that
15
+ # carries {InEffectRefinements::UNKNOWN} or an opaque module), and every condition it cannot prove is a no:
16
+ #
17
+ # - it reads the RAW refinement table, every name a `refine` argument's spelling can denote and the wildcard
18
+ # rows included, never {RefinedDispatch.resolved}'s typing subset: a decline needs only that a row may
19
+ # denote a class on the walk;
20
+ # - no in-effect row may name a class the walk cannot name, a project subclass of the receiver's class (a
21
+ # value typed as the class may be one), or a module the receiver's levels do not hold (`obj.extend(M)`,
22
+ # or a subclass's mixin);
23
+ # - walking {RefinedDispatch.levels} from the most derived, the first level or entry that is refined or
24
+ # defines the name must define it, with no `mixed` level (a project mixin RBS does not place) before it;
25
+ # - that definer must be proven ({.proven_definer?}): RBS alone is not proof.
26
+ #
27
+ # It shares the level walk with the typed arm's {RefinedDispatch.winner} and nothing else: the typer types
28
+ # only from rows the lexical lookup resolves, while a decline must count every row.
29
+ #
30
+ # Issue #1752 — `kind: :singleton` asks the same of a class object, over its singleton levels
31
+ # ({ClassObject}): `Integer.sqrt` (CRuby's own singleton method of `Integer`) answers ahead of a refinement of
32
+ # `Object#sqrt`, while a refinement of a module the class extends answers ahead of the class's methods
33
+ # further up. A singleton class's definer is proven by its own `def self.x` / `class << self` definer
34
+ # (`DefinerResolution` `Known` on the singleton side) or by the catalogue's singleton methods.
35
+ module OwnMethod # rubocop:disable Metrics/ModuleLength
36
+ # One candidate definer as `DefinerResolution`'s per-chain answer function reports it, read through the
37
+ # same four readers as the module's own hit (a call site may not name that one).
38
+ DefinerHit = Data.define(:answer, :owner, :index, :side)
39
+ EMPTY = [].freeze
40
+ private_constant :DefinerHit, :EMPTY
41
+
42
+ module_function
43
+
44
+ # Is `method_name` on an instance of `class_name` (on the class object itself for `kind: :singleton`)
45
+ # answered by the receiver's own method, ahead of every refinement the in-effect list `list` (no unknown
46
+ # marker, no opaque module) puts in effect?
47
+ def answers?(scope, class_name, method_name, list, kind: :instance)
48
+ InEffectRefinements.record_refinement_names(method_name)
49
+ raw = raw_targets(scope.discovered_refinements, method_name, list)
50
+ return true if raw.nil?
51
+
52
+ add_ancestor_spellings(scope, raw)
53
+
54
+ record_targets(scope, raw)
55
+ return false if raw.each_key.any? { |refined| InEffectRefinements.class_wildcard_key?(scope, refined) }
56
+
57
+ levels = RefinedDispatch.levels(scope, class_name, kind: kind)
58
+ return false if levels.nil?
59
+ return false if refined_off_levels?(scope, [class_name, kind], levels, raw)
60
+
61
+ shadowing_definer?(scope, [class_name, kind], levels, method_name, raw)
62
+ end
63
+
64
+ # `{refined-class key => [its in-effect refining modules]}` for every key with a row for `method_name` or a
65
+ # names-wildcard row that `list` puts in effect, by every spelling the table records, or nil when there is
66
+ # none.
67
+ def raw_targets(refinements, method_name, list)
68
+ out = nil
69
+ refinements.each do |refined, methods|
70
+ modules = in_effect_modules(methods[method_name], list) |
71
+ in_effect_modules(methods[RefinedDispatch.wildcard], list)
72
+ (out ||= {})[refined] = modules unless modules.empty?
73
+ end
74
+ out
75
+ end
76
+
77
+ # Ruby looks a constant up through the refining module's ancestors before the top level, so `refine(Bar)` in
78
+ # a module that includes `Foo` refines `Foo::Bar` when `Foo` holds one; the table records the lexical
79
+ # spellings only (`R::Bar`, `Bar`). Each declared `<ancestor>::<last segment>` joins the raw targets for the
80
+ # same refining module: over-adding only declines.
81
+ def add_ancestor_spellings(scope, raw)
82
+ raw.to_a.each do |refined, modules|
83
+ segment = refined.split("::").last
84
+ modules.each do |module_name|
85
+ ancestor_modules(scope, module_name).each do |ancestor|
86
+ spelling = "#{ancestor}::#{segment}"
87
+ next unless declared_class?(scope, spelling)
88
+
89
+ raw[spelling] = (raw[spelling] || EMPTY) | [module_name]
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # The modules `module_name` includes or prepends, transitively (`InEffectRefinements.activated_modules`, which
96
+ # records the chain's edges), itself left out.
97
+ def ancestor_modules(scope, module_name)
98
+ return EMPTY if module_name == RefinedDispatch.wildcard
99
+
100
+ ancestors = InEffectRefinements.activated_modules(scope, module_name)
101
+ ancestors.is_a?(Array) ? ancestors - [module_name] : EMPTY
102
+ end
103
+
104
+ def in_effect_modules(rows, list)
105
+ return EMPTY if rows.nil?
106
+
107
+ rows.select { |name| RefinedDispatch.in_effect?([name], list) }
108
+ end
109
+
110
+ # ADR-46 — a refined class appearing or vanishing (a new subclass declaration, a module turning up where a
111
+ # spelling named nothing), or an edit to a file declaring one (`class Kid` becoming `class Kid < Base`),
112
+ # moves the answer.
113
+ def record_targets(scope, raw)
114
+ return unless Analysis::DependencyRecorder.active?
115
+
116
+ raw.each_key do |refined|
117
+ next if refined == RefinedDispatch.wildcard
118
+
119
+ Analysis::DependencyRecorder.read_last_segment(:class, refined)
120
+ Scope::ResolutionChain.record_class(scope, refined)
121
+ end
122
+ end
123
+
124
+ # A refined class the receiver's levels do not show, but which a value typed as the receiver's class may
125
+ # still reach (critique F4): a project subclass of the class, or one whose own level mixes in a refined
126
+ # module (a module already on the class's levels is prepended again by `prepend`); a class RBS declares as
127
+ # a subclass; a module not on the levels, which `obj.extend` or a subclass's mixin can put ahead of the
128
+ # definer; and a module on the levels that some code prepends to an object's singleton class.
129
+ #
130
+ # Issue #1752 — for a class object (`receiver` `[class_name, :singleton]`) a value typed as the class object
131
+ # may be a subclass's, whose singleton level may extend a refined module; and a class is off its lookup when it
132
+ # is placed away from `Module`, of which every class object is an instance.
133
+ def refined_off_levels?(scope, (class_name, kind), levels, raw)
134
+ return true if unplaced_target?(scope, raw) || subclass_refined?(scope, class_name, raw, kind)
135
+
136
+ placed_from = kind == :singleton ? "Module" : class_name
137
+ on_levels = level_names(levels)
138
+ raw.each_key.any? do |refined|
139
+ next object_prepended?(scope, refined) if on_levels.include?(refined)
140
+ next true if module_name?(scope, refined)
141
+
142
+ declared_class?(scope, refined) && !placed_away?(scope, refined, placed_from)
143
+ end
144
+ end
145
+
146
+ # Issue #1820 — can the class `key`, off the receiver's levels, be proven never to sit at or below
147
+ # `class_name` in an object's ancestry? Only when RBS does not declare it a subclass and its whole instance
148
+ # chain is read: no cut, no mixin the tables cannot name, no superclass the tables do not know (`class K <
149
+ # <expr>`, `K = Class.new(Parent)`, a superclass RBS does not declare), and `class_name` nowhere on it.
150
+ def placed_away?(scope, key, class_name)
151
+ return false if rbs_subclass?(scope, key, class_name)
152
+ return rbs_away?(scope, key, class_name) unless project_class?(scope, key)
153
+
154
+ chain = Scope::ResolutionChain.for(scope, key, :instance, :methods)
155
+ chain.record(scope) if Analysis::DependencyRecorder.active?
156
+ return false if chain.truncated? || chain.wildcard_mixin?
157
+
158
+ chain.level_count.times.all? { |index| level_away?(scope, chain, index, class_name) }
159
+ end
160
+
161
+ # A project level is away when it is not `class_name` and its superclass is known; an external superclass
162
+ # level when RBS declares it and its RBS ancestors do not hold `class_name`.
163
+ def level_away?(scope, chain, index, class_name)
164
+ level_class = chain.level_classes[index]
165
+ return level_class != class_name && !unknown_superclass?(scope, level_class) if level_class
166
+
167
+ external = chain.level_entries(index).find(&:external?)
168
+ name = external&.candidates&.find { |candidate| scope.environment&.class_known?(candidate) }
169
+ !name.nil? && rbs_away?(scope, name, class_name)
170
+ end
171
+
172
+ def rbs_away?(scope, name, class_name)
173
+ loader = scope.environment&.rbs_loader
174
+ ancestors = loader ? loader.ancestor_names_for(name) : EMPTY
175
+ !ancestors.empty? && !ancestors.include?(class_name)
176
+ end
177
+
178
+ def project_class?(scope, name)
179
+ scope.discovered_classes.key?(name) || !(scope.discovered_class_sources[name] || EMPTY).empty? ||
180
+ scope.known_user_class?(name)
181
+ end
182
+
183
+ # A project class whose superclass the tables do not know: `class K < <expr>` (the superclass table records
184
+ # `nil`) or `K = Class.new(Parent)` (`ENVELOPE_META_SUPERCLASS_MARK`).
185
+ def unknown_superclass?(scope, name)
186
+ superclasses = scope.discovered_superclasses
187
+ return true if superclasses.key?(name) && superclasses[name].nil?
188
+
189
+ scope.discovered_parameter_envelopes[name]&.key?(Scope::DiscoveryIndex::ENVELOPE_META_SUPERCLASS_MARK) ||
190
+ false
191
+ end
192
+
193
+ # A module some code `prepend`s to an object's singleton class (or `extend`s an object with in a method
194
+ # body), which puts it ahead of the object's class even where the class already includes it.
195
+ def object_prepended?(scope, name)
196
+ scope.discovered_parameter_envelopes[name]&.key?(Scope::DiscoveryIndex::ENVELOPE_OBJECT_EXTENDED_MARK) ||
197
+ false
198
+ end
199
+
200
+ # Is some in-effect row under a class no table declares by any spelling of it (the keys sharing its last
201
+ # segment that the same refining module refines)? Ruby resolved that `refine` argument to some class, through
202
+ # a lookup the table did not follow (the refining module's own ancestors: `module R; include Foo;
203
+ # refine(Bar)` refines `Foo::Bar`) or to a class outside the analysed paths (a vendored `VendorStr <
204
+ # String`), and Rigor cannot place it in the receiver's hierarchy.
205
+ def unplaced_target?(scope, raw)
206
+ raw.any? do |refined, modules|
207
+ segment = refined.split("::").last
208
+ modules.any? do |module_name|
209
+ raw.none? do |other, others|
210
+ others.include?(module_name) && other.split("::").last == segment && declared_class?(scope, other)
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ def declared_class?(scope, name)
217
+ scope.discovered_classes.key?(name) || !(scope.discovered_class_sources[name] || EMPTY).empty? ||
218
+ scope.known_user_class?(name) || scope.environment&.class_known?(name) || false
219
+ end
220
+
221
+ def level_names(levels)
222
+ names = Set.new
223
+ levels.each do |level_class, entries, _mixed, every|
224
+ names << level_class if level_class
225
+ (every || entries).each { |entry| entry.is_a?(Array) ? names.merge(entry) : names << entry }
226
+ end
227
+ names
228
+ end
229
+
230
+ def module_name?(scope, name)
231
+ bucket = scope.discovered_parameter_envelopes[name]
232
+ return true if bucket&.key?(Scope::DiscoveryIndex::ENVELOPE_MODULE_MARK)
233
+
234
+ scope.environment&.rbs_module?(name) || false
235
+ end
236
+
237
+ def rbs_subclass?(scope, refined, class_name)
238
+ environment = scope.environment
239
+ !environment.nil? && environment.class_known?(refined) &&
240
+ environment.class_ordering(refined, class_name) == :subclass
241
+ end
242
+
243
+ # Does a project subclass of `class_name` (the `discovered_superclasses` walk `SourceArity` makes, its
244
+ # parents resolved to any class the project or RBS knows, so a subclass of a core class counts) carry a
245
+ # refined entry on its own level, or a level the walk cannot read?
246
+ #
247
+ # A subclass level the walk cannot read (a truncated chain, or a mixin the tables cannot name) hides only
248
+ # modules, so it declines only when some in-effect row names a module. `kind` `:singleton` reads each
249
+ # subclass's singleton level, whose own singleton class no refinement table key names, and which cannot place
250
+ # an `extend` the chain skipped there (`ClassObject.skipped_extend?`: it may be a `prepend` the tables record as
251
+ # one).
252
+ def subclass_refined?(scope, class_name, raw, kind = :instance)
253
+ each_subclass(scope, class_name) do |subclass|
254
+ chain = Scope::ResolutionChain.for(scope, subclass, kind, :methods)
255
+ chain.record(scope) if Analysis::DependencyRecorder.active?
256
+ if chain.truncated? || chain.wildcard_mixin? || chain.level_count.zero? ||
257
+ (kind == :singleton && singleton_level_unreadable?(scope, subclass, chain))
258
+ return true if raw.each_key.any? { |refined| module_name?(scope, refined) }
259
+
260
+ next
261
+ end
262
+ return true if chain.level_entries(0).any? { |entry| refined_entry?(entry, raw) }
263
+ end
264
+ false
265
+ end
266
+
267
+ # A subclass's singleton level is unreadable where it records an `extend` the chain skipped, or where its
268
+ # instance chain holds a mixin the tables cannot name, which an `included` hook can use to extend it.
269
+ def singleton_level_unreadable?(scope, subclass, chain)
270
+ ClassObject.skipped_extend?(scope, subclass, ClassObject.level_names(chain, 0)) ||
271
+ ClassObject.instance_unreadable?(scope, subclass)
272
+ end
273
+
274
+ def refined_entry?(entry, raw)
275
+ return entry.candidates.any? { |candidate| raw.include?(candidate) } if entry.external?
276
+ return false if entry.side == :singleton
277
+
278
+ raw.include?(entry.name)
279
+ end
280
+
281
+ # ADR-46 — every class whose subclasses are enumerated records `subclass_of:<last segment>`, which a class
282
+ # declaration with that superclass appearing or vanishing fires (`IncrementalSession#negative_affected`).
283
+ # A class whose superclass the tables do not know may be a subclass of any class, so it joins the walk too
284
+ # (`subclass_of:*`, which any class declaration appearing or vanishing fires).
285
+ def each_subclass(scope, class_name)
286
+ children = children_by_parent(scope)
287
+ record_subclasses_read(class_name)
288
+ queue = children.fetch(class_name.to_s, []) + unknown_superclass_classes(scope, class_name)
289
+ seen = {}
290
+ until queue.empty?
291
+ subclass = queue.shift
292
+ next if seen[subclass]
293
+
294
+ seen[subclass] = true
295
+ record_subclasses_read(subclass)
296
+ yield subclass
297
+ queue.concat(children.fetch(subclass, []))
298
+ end
299
+ end
300
+
301
+ def unknown_superclass_classes(scope, class_name)
302
+ Analysis::DependencyRecorder.read_name(:subclass_of, "*") if Analysis::DependencyRecorder.active?
303
+ mark = Scope::DiscoveryIndex::ENVELOPE_META_SUPERCLASS_MARK
304
+ computed = scope.discovered_superclasses.filter_map { |child, parent| child if parent.nil? }
305
+ meta = scope.discovered_parameter_envelopes.filter_map { |name, bucket| name if bucket.key?(mark) }
306
+ (computed + meta).uniq - [class_name.to_s]
307
+ end
308
+
309
+ def record_subclasses_read(class_name)
310
+ return unless Analysis::DependencyRecorder.active?
311
+
312
+ Analysis::DependencyRecorder.read_last_segment(:subclass_of, class_name.to_s)
313
+ end
314
+
315
+ def children_by_parent(scope)
316
+ scope.discovered_superclasses.each_with_object({}) do |(child, raw_parent), children|
317
+ parent = scope.ancestor_name_candidates(child, raw_parent).find { |name| known_class?(scope, name) }
318
+ (children[parent] ||= []) << child if parent
319
+ end
320
+ end
321
+
322
+ def known_class?(scope, name)
323
+ scope.known_user_class?(name) || scope.discovered_parameter_envelopes.key?(name) ||
324
+ scope.environment&.class_known?(name) || false
325
+ end
326
+
327
+ # Walks the levels from the most derived: true when the first level or entry that a raw target names or
328
+ # that defines `method_name` defines it, and that definer is proven. A refined level or entry (an entry no
329
+ # table names by any of its candidate names), a `mixed` level (a project mixin RBS does not place, which
330
+ # may be prepended or refined), an unproven definer, and the end of the walk all answer false. An entry no
331
+ # table names is passed over otherwise: it cannot be proven a definer, and a definer the walk misses there
332
+ # sits ahead of every refinement the walk then reaches.
333
+ #
334
+ # Issue #1752 — a singleton level with a refined module answers false whatever its order: the tables record
335
+ # `class << self; prepend M` as an `extend`, so the module may sit ahead of the class's own singleton methods.
336
+ def shadowing_definer?(scope, receiver, levels, method_name, raw)
337
+ levels.each do |level_class, entries, mixed, every|
338
+ return false if mixed || (level_class && raw.include?(level_class))
339
+
340
+ row = every || entries
341
+ return false if level_class.is_a?(SingletonClass) && singleton_level_refined?(row, raw)
342
+
343
+ row.each do |entry|
344
+ case entry_answer(scope, entry, level_class, method_name, raw)
345
+ when :refined then return false
346
+ when :defines then return proven_definer?(scope, receiver, entry, method_name)
347
+ end
348
+ end
349
+ end
350
+ false
351
+ end
352
+
353
+ def singleton_level_refined?(row, raw)
354
+ row.any? do |entry|
355
+ next entry.any? { |candidate| raw.include?(candidate) } if entry.is_a?(Array)
356
+
357
+ !entry.is_a?(SingletonClass) && raw.include?(entry)
358
+ end
359
+ end
360
+
361
+ # `:refined`, `:defines`, or nil to move on. The level's own class was asked before its entries.
362
+ def entry_answer(scope, entry, level_class, method_name, raw)
363
+ return entry.any? { |candidate| raw.include?(candidate) } ? :refined : nil if entry.is_a?(Array)
364
+ return :refined if entry != level_class && raw.include?(entry)
365
+
366
+ :defines if RefinedDispatch.defines?(scope, entry, method_name)
367
+ end
368
+
369
+ # Is Ruby known to find `method_name` on `entry`, the first definer the walk reached for an instance of
370
+ # `class_name`? Neither proof stands on a class that rewrites its method table in a way no literal names
371
+ # (`ENVELOPE_DYNAMIC_MARK`), nor for a name the project removes from it with a literal `undef`,
372
+ # `remove_method` or `undef_method` anywhere (`ENVELOPE_REMOVED_KIND`), whatever defined it.
373
+ #
374
+ # - A project `def`, `attr_*` or `define_method` (ADR-119 A7): `DefinerResolution` must answer `Known` with
375
+ # `entry` as the owner, on the receiver's chain, so a definer that may not have run (a `def` under `if`,
376
+ # which Ruby skips, leaving the ancestor's refinement to answer) or that an unplaced mixin or fork may
377
+ # precede is not proof. A `def` whose envelope something made opaque (`remove_method`, `undef`, an
378
+ # alias, a wrapping macro) is not proof either.
379
+ # - A core method the offline CRuby catalogue lists on `entry` itself ({Builtins::CRubyDefiners}), which the
380
+ # project has not removed or rewritten there (A9: no opaque envelope for the name, which `remove_method`
381
+ # and `undef` record). Core RBS is not proof: it redeclares some inherited methods on a subclass
382
+ # (`Integer#quo`, `File#to_path`, owned by `Numeric` and `IO`) and declares some CRuby no longer defines
383
+ # (`Process::Status#&`).
384
+ #
385
+ # ADR-46 — the envelope bucket records the files declaring `entry`, and the class edge a new file reopening
386
+ # it (to `remove_method` the name, say) fires.
387
+ #
388
+ # Issue #1752 — a {SingletonClass} entry is proven on its class's singleton side: its own singleton definer,
389
+ # `Known` on the class object's chain, or the catalogue's singleton methods. Any other entry of a class
390
+ # object's walk (an extended module; `Class`, `Module`, `Object`, `Kernel`) is proven as an instance-side
391
+ # entry is, with the certainty read over the class object's chain.
392
+ def proven_definer?(scope, receiver, entry, method_name)
393
+ owner, side = entry.is_a?(SingletonClass) ? [entry.of, :singleton] : [entry, :instance]
394
+ Analysis::DependencyRecorder.read_last_segment(:class, owner) if Analysis::DependencyRecorder.active?
395
+ bucket = scope.parameter_envelopes_of(owner)
396
+ return false if bucket.key?(Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK)
397
+ return false if bucket.key?([Scope::DiscoveryIndex::ENVELOPE_REMOVED_KIND, method_name.to_sym])
398
+
399
+ opaque = Source::ParameterEnvelope.opaque?(bucket[[side, method_name.to_sym]])
400
+ return true if project_definer?(scope, receiver, [owner, side], method_name, opaque)
401
+ return false if opaque
402
+
403
+ if side == :singleton
404
+ Builtins::CRubyDefiners.singleton_defines?(owner, method_name)
405
+ else
406
+ Builtins::CRubyDefiners.defines?(owner, method_name)
407
+ end
408
+ end
409
+
410
+ # A `def` counts while its envelope is its own (an alias or a wrapping macro makes it opaque); an `attr_*` or
411
+ # `define_method` always records an opaque one, so for those the existence table and the certainty read are
412
+ # the proof, beside the removal check above. A singleton name the extends fold copied from an extended module
413
+ # is that module's (`ClassObject.own_singleton_definer?`).
414
+ def project_definer?(scope, receiver, (owner, side), method_name, opaque)
415
+ own = side == :singleton ? own_singleton_kind(scope, owner, method_name) : own_kind(scope, owner, method_name)
416
+ return false if own.nil? || (own == :def && opaque)
417
+
418
+ certain_owner?(scope, receiver, owner, method_name)
419
+ end
420
+
421
+ # `:def` for a `def` of the owner's own, `:other` for another definer the existence table records, or nil.
422
+ def own_kind(scope, owner, method_name)
423
+ return :def if scope.user_def_for(owner, method_name)
424
+
425
+ :other if scope.discovered_method?(owner, method_name, :instance)
426
+ end
427
+
428
+ def own_singleton_kind(scope, owner, method_name)
429
+ node = scope.singleton_def_for(owner, method_name)
430
+ return :def if node && !ClassObject.fold_copy?(scope, owner, method_name)
431
+
432
+ :other if ClassObject.own_singleton_definer?(scope, owner, method_name)
433
+ end
434
+
435
+ # ADR-119 WD2 — the candidate-set read over the receiver's chain (the class object's for `kind` `:singleton`),
436
+ # with every project `def`, `attr_*` and `define_method` as a candidate: true only for a `Known` answer whose
437
+ # owner is `owner`.
438
+ def certain_owner?(scope, (class_name, kind), owner, method_name)
439
+ case DefinerResolution.resolve(scope, class_name, method_name, kind, question: :definer,
440
+ &definer_answer(scope, method_name))
441
+ in DefinerResolution::Known(answer: _answer, owner: found) then found == owner
442
+ in DefinerResolution::UNKNOWN then false
443
+ in DefinerResolution::ABSENT then false # rubocop:disable Lint/DuplicateBranch
444
+ end
445
+ end
446
+
447
+ # The first project entry at or after `from` that defines `method_name` on its side, by a `def` or by another
448
+ # definer the existence table records (`attr_*`, `define_method`; an `attr_*` or `define_method` in
449
+ # `class << self` on a singleton entry, while a body's `define_singleton_method` records no existence entry
450
+ # and proves nothing). A singleton name the extends fold copied is asked past by `DefinerResolution` itself,
451
+ # so the module that wrote it answers at its own position.
452
+ def definer_answer(scope, method_name)
453
+ lambda do |chain, from|
454
+ index = from - 1
455
+ chain.search(scope, from) do |entry|
456
+ index += 1
457
+ next if entry.external? || !entry_defines?(scope, entry, method_name)
458
+
459
+ DefinerHit.new(entry.name, entry.name, index, entry.side)
460
+ end
461
+ end
462
+ end
463
+
464
+ def entry_defines?(scope, entry, method_name)
465
+ name = entry.name
466
+ if entry.side == :singleton
467
+ scope.singleton_def_for(name, method_name) || scope.discovered_method?(name, method_name, :singleton)
468
+ else
469
+ scope.user_def_for(name, method_name) || scope.discovered_method?(name, method_name, :instance)
470
+ end
471
+ end
472
+ end
473
+ end
474
+ end
475
+ end