rigortype 0.4.4 → 0.4.6

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 (233) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/docs/handbook/11-sig-gen.md +100 -18
  4. data/docs/manual/02-cli-reference.md +85 -16
  5. data/docs/manual/03-configuration.md +12 -9
  6. data/docs/manual/04-diagnostics.md +17 -6
  7. data/docs/manual/05-inspecting-types.md +2 -2
  8. data/docs/manual/06-baseline.md +1 -1
  9. data/docs/manual/07-plugins.md +2 -1
  10. data/docs/manual/08-skills.md +7 -0
  11. data/docs/manual/10-mcp-server.md +17 -2
  12. data/docs/manual/12-caching.md +8 -1
  13. data/docs/manual/18-removing-dead-code.md +18 -7
  14. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  15. data/docs/manual/plugins/rigor-actionpack.md +30 -0
  16. data/docs/manual/plugins/rigor-devise.md +15 -0
  17. data/docs/manual/plugins/rigor-rspec.md +102 -1
  18. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  19. data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
  20. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +30 -9
  21. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +142 -15
  22. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +115 -7
  23. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +253 -0
  24. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  25. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  26. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  27. data/lib/rigor/analysis/check_rules/rule_ids.rb +3 -1
  28. data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
  29. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +64 -4
  30. data/lib/rigor/analysis/check_rules.rb +319 -21
  31. data/lib/rigor/analysis/dependency_recorder.rb +11 -0
  32. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  33. data/lib/rigor/analysis/edit_replay.rb +67 -0
  34. data/lib/rigor/analysis/incremental.rb +86 -0
  35. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
  36. data/lib/rigor/analysis/incremental_session.rb +336 -46
  37. data/lib/rigor/analysis/path_expansion.rb +44 -1
  38. data/lib/rigor/analysis/reachability/graph.rb +90 -13
  39. data/lib/rigor/analysis/reachability/scan.rb +279 -39
  40. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  41. data/lib/rigor/analysis/rule_catalog.rb +22 -5
  42. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  43. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  44. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  45. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +11 -3
  46. data/lib/rigor/analysis/runner/pool_coordinator.rb +82 -16
  47. data/lib/rigor/analysis/runner/project_pre_passes.rb +45 -3
  48. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  49. data/lib/rigor/analysis/runner.rb +222 -111
  50. data/lib/rigor/analysis/severity_stamp.rb +11 -0
  51. data/lib/rigor/analysis/worker_session.rb +24 -1
  52. data/lib/rigor/cache/descriptor.rb +40 -7
  53. data/lib/rigor/cache/engine_source.rb +3 -2
  54. data/lib/rigor/cache/incremental_snapshot.rb +49 -8
  55. data/lib/rigor/cache/rbs_descriptor.rb +2 -1
  56. data/lib/rigor/cache/store.rb +4 -3
  57. data/lib/rigor/cli/check_command.rb +93 -4
  58. data/lib/rigor/cli/command.rb +3 -1
  59. data/lib/rigor/cli/diff_command.rb +2 -1
  60. data/lib/rigor/cli/docs_command.rb +3 -2
  61. data/lib/rigor/cli/doctor_command.rb +1 -1
  62. data/lib/rigor/cli/explain_command.rb +3 -2
  63. data/lib/rigor/cli/lens_command.rb +5 -5
  64. data/lib/rigor/cli/mcp_command.rb +2 -1
  65. data/lib/rigor/cli/options.rb +20 -3
  66. data/lib/rigor/cli/plugin_command.rb +2 -1
  67. data/lib/rigor/cli/plugins_command.rb +7 -1
  68. data/lib/rigor/cli/plugins_renderer.rb +24 -2
  69. data/lib/rigor/cli/show_bleedingedge_command.rb +2 -1
  70. data/lib/rigor/cli/sig_gen_command.rb +73 -13
  71. data/lib/rigor/cli/skill_command.rb +2 -1
  72. data/lib/rigor/cli/skill_describe.rb +3 -2
  73. data/lib/rigor/cli/type_of_command.rb +2 -1
  74. data/lib/rigor/cli/type_of_project_scope.rb +27 -0
  75. data/lib/rigor/cli/unused_command.rb +10 -7
  76. data/lib/rigor/cli.rb +3 -2
  77. data/lib/rigor/config_audit.rb +72 -16
  78. data/lib/rigor/configuration/severity_profile.rb +20 -2
  79. data/lib/rigor/configuration.rb +43 -7
  80. data/lib/rigor/effects/signature_sources.rb +3 -1
  81. data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
  82. data/lib/rigor/environment/gated_signature_guard.rb +2 -1
  83. data/lib/rigor/environment/gated_signature_plan.rb +2 -1
  84. data/lib/rigor/environment/installed_gem_set.rb +2 -1
  85. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
  86. data/lib/rigor/environment/rbs_loader.rb +136 -7
  87. data/lib/rigor/environment/required_features.rb +136 -29
  88. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  89. data/lib/rigor/environment.rb +4 -2
  90. data/lib/rigor/inference/block_call_timing.rb +7 -1
  91. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  92. data/lib/rigor/inference/callee_mutation_chain.rb +115 -0
  93. data/lib/rigor/inference/captured_locals.rb +44 -13
  94. data/lib/rigor/inference/clause_widening.rb +90 -0
  95. data/lib/rigor/inference/deep_erasure.rb +138 -0
  96. data/lib/rigor/inference/def_return_typer.rb +159 -9
  97. data/lib/rigor/inference/definer_resolution.rb +101 -20
  98. data/lib/rigor/inference/error_info.rb +17 -3
  99. data/lib/rigor/inference/expression_typer.rb +97 -18
  100. data/lib/rigor/inference/global_write_census.rb +36 -1
  101. data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
  102. data/lib/rigor/inference/ivar_writer_census.rb +195 -0
  103. data/lib/rigor/inference/loop_value_record.rb +53 -0
  104. data/lib/rigor/inference/macro_block_self_type.rb +20 -6
  105. data/lib/rigor/inference/match_rebinding/frame.rb +19 -0
  106. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +278 -42
  107. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +6 -2
  108. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
  109. data/lib/rigor/inference/method_dispatcher/set_folding.rb +53 -20
  110. data/lib/rigor/inference/method_dispatcher.rb +113 -19
  111. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  112. data/lib/rigor/inference/module_function_state.rb +149 -42
  113. data/lib/rigor/inference/mutation_widening.rb +5 -2
  114. data/lib/rigor/inference/narrowing.rb +116 -11
  115. data/lib/rigor/inference/object_mixins.rb +13 -1
  116. data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
  117. data/lib/rigor/inference/project_patched_methods.rb +168 -4
  118. data/lib/rigor/inference/project_patched_mixins.rb +162 -0
  119. data/lib/rigor/inference/project_patched_scanner.rb +51 -12
  120. data/lib/rigor/inference/project_patched_visibility.rb +214 -0
  121. data/lib/rigor/inference/project_seed_tables.rb +121 -0
  122. data/lib/rigor/inference/project_yielder.rb +128 -0
  123. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
  124. data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
  125. data/lib/rigor/inference/reflective_ivar_write.rb +6 -6
  126. data/lib/rigor/inference/return_barrier.rb +16 -0
  127. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -0
  128. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  129. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +162 -0
  130. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  131. data/lib/rigor/inference/scope_indexer.rb +1144 -197
  132. data/lib/rigor/inference/self_call_ivar_writes.rb +29 -12
  133. data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
  134. data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
  135. data/lib/rigor/inference/statement_evaluator.rb +1088 -213
  136. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  137. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  138. data/lib/rigor/inference/synthetic_method_scanner.rb +72 -12
  139. data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
  140. data/lib/rigor/language_server/buffer_table.rb +6 -0
  141. data/lib/rigor/language_server/diagnostic_publisher.rb +20 -6
  142. data/lib/rigor/language_server/project_context.rb +59 -3
  143. data/lib/rigor/language_server/server.rb +5 -3
  144. data/lib/rigor/lens/contract_reader.rb +45 -2
  145. data/lib/rigor/lens/document.rb +2 -1
  146. data/lib/rigor/lens/file_reader.rb +12 -2
  147. data/lib/rigor/lens/members.rb +16 -2
  148. data/lib/rigor/lens/run.rb +34 -7
  149. data/lib/rigor/mcp/server.rb +11 -3
  150. data/lib/rigor/plugin/base.rb +29 -13
  151. data/lib/rigor/plugin/io_boundary.rb +42 -6
  152. data/lib/rigor/plugin/isolation.rb +4 -3
  153. data/lib/rigor/plugin/manifest.rb +29 -3
  154. data/lib/rigor/plugin/registry.rb +38 -0
  155. data/lib/rigor/protection/discovery_seed.rb +64 -36
  156. data/lib/rigor/protection/mutation_cache.rb +8 -2
  157. data/lib/rigor/rooted_glob.rb +36 -0
  158. data/lib/rigor/scope/discovery_index.rb +5 -0
  159. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
  160. data/lib/rigor/scope/resolution_chain.rb +40 -5
  161. data/lib/rigor/scope.rb +59 -7
  162. data/lib/rigor/sig_gen/alias_index.rb +2 -1
  163. data/lib/rigor/sig_gen/classification.rb +4 -1
  164. data/lib/rigor/sig_gen/generator.rb +465 -49
  165. data/lib/rigor/sig_gen/layout_index.rb +5 -2
  166. data/lib/rigor/sig_gen/observation_collector.rb +141 -12
  167. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  168. data/lib/rigor/sig_gen/renderer.rb +16 -155
  169. data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
  170. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  171. data/lib/rigor/sig_gen/write_reporter.rb +203 -0
  172. data/lib/rigor/sig_gen/write_result.rb +3 -1
  173. data/lib/rigor/sig_gen/writer.rb +5 -3
  174. data/lib/rigor/signature_path_audit.rb +4 -2
  175. data/lib/rigor/type/combinator.rb +4 -3
  176. data/lib/rigor/type/constant.rb +54 -3
  177. data/lib/rigor/type/hash_shape.rb +14 -9
  178. data/lib/rigor/type/struct_instance.rb +3 -2
  179. data/lib/rigor/version.rb +1 -1
  180. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
  181. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +61 -44
  182. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +20 -1
  183. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
  184. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
  185. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  186. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +13 -0
  187. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
  188. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
  189. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
  190. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -2
  191. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
  192. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
  193. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
  194. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  195. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  196. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  197. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +38 -9
  198. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
  199. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
  200. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
  201. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
  202. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
  203. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
  204. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  205. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  206. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
  207. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +4 -2
  208. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
  209. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
  210. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
  211. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  212. data/sig/rigor/analysis/baseline.rbs +3 -2
  213. data/sig/rigor/effects/file_collection.rbs +5 -6
  214. data/sig/rigor/effects/registry.rbs +8 -6
  215. data/sig/rigor/effects/summary.rbs +6 -5
  216. data/sig/rigor/environment.rbs +5 -3
  217. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  218. data/sig/rigor/inference.rbs +6 -0
  219. data/sig/rigor/plugin/additional_initializer.rbs +5 -5
  220. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  221. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  222. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  223. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  224. data/sig/rigor/plugin/io_boundary.rbs +5 -2
  225. data/sig/rigor/plugin/manifest.rbs +16 -18
  226. data/sig/rigor/plugin/protocol_contract.rbs +10 -9
  227. data/sig/rigor/plugin/registry.rbs +1 -1
  228. data/sig/rigor/rooted_glob.rbs +7 -0
  229. data/sig/rigor/scope.rbs +34 -52
  230. data/sig/rigor/type.rbs +0 -6
  231. data/skills/rigor-doctor/references/01-checks.md +23 -11
  232. data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
  233. metadata +32 -1
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "prism"
4
4
 
5
+ require_relative "../analysis/dependency_recorder"
5
6
  require_relative "../source/node_children"
6
7
  require_relative "../source/parameter_envelope"
7
8
  require_relative "../type"
@@ -139,10 +140,11 @@ module Rigor
139
140
  # Issue #1596 — gives up when `self`'s class, a project subclass, or a project ancestor or module on either's
140
141
  # chain defines a {PROTOCOL_NAMES} method (a `def`, `define_method`, `attr_*` or alias), rewrites its methods
141
142
  # beyond the names it spells, or is refined on one; or when either chain holds an external ancestor, whose
142
- # own definitions the read cannot see.
143
+ # own definitions the read cannot see. Issue #1979 — a class built on a core constructor call
144
+ # (`class Fact < Data.define(:a)`) cannot be `self`'s class or a subclass of it, so the walk leaves it out.
143
145
  def no_protocol_override
144
146
  classes = [@class_name]
145
- RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name) { |subclass| classes << subclass }
147
+ RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name, possible_only: true) { |subclass| classes << subclass }
146
148
  classes.each do |class_name|
147
149
  chain = Scope::ResolutionChain.for(@scope, class_name.to_s, :instance, :methods)
148
150
  unprovable! if chain.truncated? || chain.wildcard_mixin? || !settled?(chain)
@@ -185,7 +187,16 @@ module Rigor
185
187
 
186
188
  # Whether a `refine` block anywhere in the project defines `name`, on any class: a `using` in effect where the
187
189
  # read code sits may route the call to it (`refine Kernel do def warn …`), whatever the receiver.
190
+ #
191
+ # Issue #2163 — the answer reads every file's refinements whole, and a refined class off the chain (`Kernel`,
192
+ # `IO`, `String`) is no `mark:` the chain read files, so it records `refinement:<name>` and `refinement:*`
193
+ # whichever way it answers, as `InEffectRefinements` consumers do: a file that starts or stops refining the
194
+ # name re-checks the reader.
188
195
  def refined?(name)
196
+ if Analysis::DependencyRecorder.active?
197
+ Analysis::DependencyRecorder.read_name(:refinement, name)
198
+ Analysis::DependencyRecorder.read_name(:refinement, Scope::DiscoveryIndex::REFINEMENT_WILDCARD)
199
+ end
189
200
  @scope.discovered_refinements.any? { |_refined, methods| methods.key?(name) }
190
201
  end
191
202
 
@@ -229,7 +240,7 @@ module Rigor
229
240
 
230
241
  # A subclass whose superclass the tables do not know is enumerated too, and its chain ends short of the
231
242
  # owner; one that reaches no definer of its own leaves the call to its superclass.
232
- RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name) do |subclass|
243
+ RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name, possible_only: true) do |subclass|
233
244
  unprovable! unless [owner, nil].include?(definer_ahead(subclass, name, owner))
234
245
  end
235
246
  def_node
@@ -239,12 +250,11 @@ module Rigor
239
250
  # one file, with one `def` of the name there (a reopening or a `class_eval` body in the same file is a second
240
251
  # one), and no `define_method`, `attr_*` or alias of it, nor a rewrite no literal names (`T.define_method`,
241
252
  # `T.send(:define_method, …)`, `T.class_eval` with a computed name), nor a definition the walk could not prove
242
- # runs (a possible or contested one, as `DefinerResolution` reads them). The class edge files every declaring
243
- # file.
253
+ # runs (a possible or contested one, as `DefinerResolution` reads them). `Scope#parameter_envelopes_of` files
254
+ # the class edge to every declaring file and, issue #2137, the `mark:` edge for a file that only marks it.
244
255
  def single_definition(owner, name)
245
- Scope::ResolutionChain.record_class(@scope, owner) if Analysis::DependencyRecorder.active?
256
+ bucket = @scope.parameter_envelopes_of(owner)
246
257
  unprovable! if possible_definition?(owner, name)
247
- bucket = @scope.discovered_parameter_envelopes[owner] || {}
248
258
  unprovable! if bucket[[:instance, name]] == Source::ParameterEnvelope::OPAQUE
249
259
  unprovable! if (@scope.discovered_class_sources[owner]&.size || 0) > 1
250
260
  unprovable! if definition_count(owner, name) > 1
@@ -258,9 +268,11 @@ module Rigor
258
268
  end
259
269
 
260
270
  def definition_count(owner, name)
261
- @scope.discovered_deferred_ranges.sum do |_path, rows|
262
- rows.count { |row| row[2] == name && row[3] == :instance && row[4] == owner }
271
+ count = 0
272
+ @scope.discovered_deferred_ranges.each_value do |rows|
273
+ count += rows.count { |row| row[2] == name && row[3] == :instance && row[4] == owner }
263
274
  end
275
+ count
264
276
  end
265
277
 
266
278
  # Whether `chain`'s own order stands with no second world: no order mark and no fork. `ResolutionChain#settle`
@@ -276,9 +288,12 @@ module Rigor
276
288
  end
277
289
 
278
290
  # Whether the project rewrites `class_name`'s method table beyond the names it spells, or refines it.
291
+ #
292
+ # Issue #2137 — read through `Scope#parameter_envelopes_of`, which files the `mark:` edge: a `refine` of the
293
+ # class from a file whose declared classes do not change fires no class key, and adding or removing it must
294
+ # re-check the reader.
279
295
  def rewritten?(class_name)
280
- bucket = @scope.discovered_parameter_envelopes[class_name]
281
- !bucket.nil? && Scope::DiscoveryIndex.rewritten_surface?(bucket)
296
+ Scope::DiscoveryIndex.rewritten_surface?(@scope.parameter_envelopes_of(class_name))
282
297
  end
283
298
 
284
299
  # The first project class or module on `class_name`'s settled instance chain that defines `name`, stopping
@@ -326,8 +341,10 @@ module Rigor
326
341
  return nil if loader.nil? || !loader.core_or_stdlib_class?(type.class_name)
327
342
 
328
343
  segment = node.name.to_s
344
+ suffix = "::#{segment}"
329
345
  project = [@scope.discovered_classes, @scope.in_source_constants]
330
- return nil if project.any? { |table| table.each_key.any? { |name| name.to_s.split("::").last == segment } }
346
+ ends_with_segment = ->(name) { (name = name.to_s) == segment || name.end_with?(suffix) }
347
+ return nil if project.any? { |table| table.each_key.any?(&ends_with_segment) }
331
348
 
332
349
  type
333
350
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Inference
5
+ # Issue #1572 — the singleton typing site's external-ahead decline. That site still answers from
6
+ # {Scope#singleton_def_through_ancestors}, which passes the chain's external entries over, so `extend M; extend
7
+ # Enumerable` typed `C.to_a` from `M#to_a` although Ruby calls `Enumerable#to_a` (`C.singleton_class.ancestors`
8
+ # starts `[#<Class:C>, Enumerable, M]`). The instance typing site reads `DefinerResolution`, which already
9
+ # declines there.
10
+ #
11
+ # {.withhold?} is true when the read must withhold (the call types `Dynamic`): the singleton chain holds no
12
+ # project entry that writes the name; the reader's `owner` has no project entry on it; or an external entry ahead
13
+ # of the later of the two may answer first, because RBS does not know it or declares the name in it (the test
14
+ # `DefinerResolution` applies, `ResolutionChain::Relevance.external_lacks_on?`). The later of the two, because
15
+ # where the chain does not stand the reader answers master's owner. An entry writes the name with its own `def`,
16
+ # never a copy the extends fold put on a class object (`RefinedDispatch::ClassObject.fold_copy?`, which also
17
+ # matches a copy by location where the two tables came from two parses of the file), so a folded `M#to_a`
18
+ # answers at `M`'s own position.
19
+ #
20
+ # ADR-46: the search files the class edge of the root and of every project entry ahead of the definer, and each
21
+ # tested external files the negative class edge on its spelling's last segment, as `DefinerResolution` does.
22
+ module SingletonExternalAhead
23
+ module_function
24
+
25
+ def withhold?(scope, class_name, method_name, owner)
26
+ chain = Scope::ResolutionChain.for(scope, class_name.to_s, :singleton, :methods)
27
+ definer = first_writer_index(scope, chain, method_name)
28
+ return true if definer.nil?
29
+
30
+ owner_index = chain.entries.index { |entry| !entry.external? && entry.name == owner.to_s }
31
+ return true if owner_index.nil?
32
+
33
+ external_ahead_may_answer?(scope, chain, method_name, [definer, owner_index].max)
34
+ end
35
+
36
+ def first_writer_index(scope, chain, method_name)
37
+ index = -1
38
+ chain.search(scope) do |entry|
39
+ index += 1
40
+ !entry.external? && writes_name?(scope, entry, method_name)
41
+ end && index
42
+ end
43
+
44
+ def writes_name?(scope, entry, method_name)
45
+ return !scope.user_def_for(entry.name, method_name).nil? unless entry.side == :singleton
46
+
47
+ !scope.singleton_def_for(entry.name, method_name).nil? &&
48
+ !RefinedDispatch::ClassObject.fold_copy?(scope, entry.name, method_name)
49
+ end
50
+
51
+ def external_ahead_may_answer?(scope, chain, method_name, stop)
52
+ recording = Analysis::DependencyRecorder.active?
53
+ chain.entries.first(stop).any? do |entry|
54
+ next false unless entry.external?
55
+
56
+ Analysis::DependencyRecorder.read_missing(:class, entry.raw.to_s.split("::").last) if recording
57
+ !Scope::ResolutionChain::Relevance.external_lacks_on?(scope, entry.candidates, entry.side, method_name)
58
+ end
59
+ end
60
+
61
+ private_class_method :first_writer_index, :writes_name?, :external_ahead_may_answer?
62
+ end
63
+ end
64
+ end
@@ -109,6 +109,13 @@ module Rigor
109
109
  nesting&.first
110
110
  end
111
111
 
112
+ # Issue #1722 — the hook-capable test for one chain entry, asked by `call.undefined-method` of a project module
113
+ # mixed into a core class: a module whose `self.included(base)` hook writes `base.class_eval { def x }` gives
114
+ # the includer methods no table records.
115
+ def entry_hook_capable?(scope, entry, flavor = :methods)
116
+ Context.new(scope, flavor).capable?(entry)
117
+ end
118
+
112
119
  def tested_levels(chain, from, bound)
113
120
  starts = chain.level_starts
114
121
  size = chain.entries.size