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,320 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Inference
5
+ # ADR-121 WD2 (issue #1664) — which in-effect refinement, if any, answers a call on an instance of a class. The
6
+ # typed refined arm (`ExpressionTyper#try_refined_dispatch`) asks it before every other answer, because those
7
+ # answer from the method a refinement replaces.
8
+ #
9
+ # Precedence follows `doc/syntax/refinements.rdoc` § Method Lookup: walk the receiver's classes from the most
10
+ # derived. At each class, the latest in-effect module refining the name for that class wins; otherwise its
11
+ # prepended modules, its own method and its included modules answer in that order, and the refinement
12
+ # declines; otherwise move on. A refinement of a module is decided where that module sits in the walk.
13
+ #
14
+ # The walk reads RBS ancestors for a class RBS knows (each ancestor a level of its own, so a core class's
15
+ # prepends are not told apart), and the project's `Scope::ResolutionChain` levels for one only the project
16
+ # declares, splicing in the RBS ancestors of the superclass it does not declare. "Defines the method" is an RBS
17
+ # declaration on that class itself, or a project `def`, `attr_*` or `define_method` of it. Core RBS sometimes
18
+ # redeclares an inherited method on a subclass, which stops the walk there: a known imprecision, in the
19
+ # declining direction.
20
+ #
21
+ # Issue #1740 — {OwnMethod} shares {.levels} with the walk below and asks the opposite question for the two
22
+ # argument checks: does the receiver's own method answer ahead of every refinement in effect?
23
+ #
24
+ # Issue #1752 — a class object is walked through its own lookup ({ClassObject}): its singleton class and each
25
+ # superclass's, then `Class`, `Module`, `Object`, `Kernel` and `BasicObject`, so a refinement of `Object` answers
26
+ # `Integer.oo` while `Integer.sqrt` keeps answering ahead of one.
27
+ module RefinedDispatch
28
+ # The refinement that answers: `module_name` refines `refined_class` (a class or module on the walk).
29
+ Winner = Data.define(:module_name, :refined_class)
30
+
31
+ # Issue #1752 — the singleton class `#<Class:of>` as an entry of a class object's walk ({ClassObject}): it
32
+ # defines the name through `of`'s own singleton methods, and no refinement table key names it.
33
+ SingletonClass = Data.define(:of)
34
+
35
+ # The answer when the in-effect list carries {InEffectRefinements::UNKNOWN} and some refinement defines the
36
+ # name on a class the walk reaches: any of them may answer.
37
+ UNKNOWN = :unknown
38
+
39
+ NO_TARGETS = {}.freeze
40
+ private_constant :NO_TARGETS
41
+
42
+ module_function
43
+
44
+ # `Scope::DiscoveryIndex::REFINEMENT_WILDCARD`, read when asked: this file loads before `Scope`.
45
+ def wildcard = Scope::DiscoveryIndex::REFINEMENT_WILDCARD
46
+
47
+ # A {Winner}, {UNKNOWN}, or nil when no in-effect refinement answers `method_name` on an instance of
48
+ # `class_name` (the class's own lookup does), or with `kind: :singleton` on the class object `class_name`
49
+ # itself (issue #1752). `list` is the call site's in-effect refinements.
50
+ #
51
+ # ADR-121 WD7 — a row the walk could not read never yields a {Winner}: an in-effect module's row for the name
52
+ # whose class the walk could not name reaches every receiver, and a names-wildcard row of a level the walk
53
+ # reaches before a definer may define the name, so both answer {UNKNOWN}. So does a winner whose module shares
54
+ # its last segment with another listed module (A1): which of a `using`'s declared candidates Ruby finds can
55
+ # depend on load order. A class object's walk always ends at `Object`, so where it cannot be read a row that
56
+ # may sit on it answers {UNKNOWN} too.
57
+ def winner(scope, class_name, method_name, list, kind: :instance)
58
+ refinements = scope.discovered_refinements
59
+ return UNKNOWN if class_unknown_row?(scope, refinements, method_name, list)
60
+
61
+ targets = resolved(scope, targets(refinements, method_name, list), method_name)
62
+ unread = targets(refinements, wildcard, list)
63
+ unknown = list.include?(InEffectRefinements::UNKNOWN)
64
+ return nil if targets.nil? && unread.nil? && !unknown
65
+
66
+ levels = levels(scope, class_name, kind: kind)
67
+ return unread_class_object(scope, refinements, method_name, list) if levels.nil? && kind == :singleton
68
+ return nil if levels.nil?
69
+ return UNKNOWN if unknown && unknown_reaches?(scope, refinements, method_name, levels)
70
+
71
+ walk_targets(scope, levels, method_name, [targets, unread], list)
72
+ end
73
+
74
+ # {UNKNOWN} when some row for `method_name` (or a names-wildcard row) that `list` puts in effect, or any row
75
+ # under the unknown marker, names a class or module a class object may reach ({ClassObject.reachable?}).
76
+ def unread_class_object(scope, refinements, method_name, list)
77
+ unknown = list.include?(InEffectRefinements::UNKNOWN)
78
+ reaches = refinements.any? do |refined, methods|
79
+ rows = [methods[method_name], methods[wildcard]].compact
80
+ !rows.empty? && (unknown || rows.any? { |modules| in_effect?(modules, list) }) &&
81
+ ClassObject.reachable?(scope, refined)
82
+ end
83
+ reaches ? UNKNOWN : nil
84
+ end
85
+
86
+ def walk_targets(scope, levels, method_name, (targets, unread), list)
87
+ return nil if targets.nil? && unread.nil?
88
+
89
+ answer = walk(scope, levels, method_name, targets || NO_TARGETS, unread || NO_TARGETS)
90
+ answer.is_a?(Winner) && rival_spelling?(answer.module_name, list) ? UNKNOWN : answer
91
+ end
92
+
93
+ # ADR-121 WD7 (A2) — does an in-effect module have a row for `method_name` (or a names-wildcard row) whose
94
+ # class the walk could not name ({InEffectRefinements.class_wildcard_key?})? It may refine the receiver's class.
95
+ def class_unknown_row?(scope, refinements, method_name, list)
96
+ refinements.any? do |refined, methods|
97
+ rows = [methods[method_name], methods[wildcard]].compact
98
+ !rows.empty? && rows.any? { |modules| in_effect?(modules, list) } &&
99
+ InEffectRefinements.class_wildcard_key?(scope, refined)
100
+ end
101
+ end
102
+
103
+ # Is one of `modules` (a row's refining modules) in `list`? The wildcard module, which the walk could not name,
104
+ # is any listed module.
105
+ def in_effect?(modules, list)
106
+ return list.any? { |name| name != InEffectRefinements::UNKNOWN } if modules.include?(wildcard)
107
+
108
+ modules.any? { |name| list.include?(name) }
109
+ end
110
+
111
+ def rival_spelling?(module_name, list)
112
+ segment = module_name.split("::").last
113
+ list.any? { |other| other.is_a?(String) && other != module_name && other.split("::").last == segment }
114
+ end
115
+
116
+ # `{refined class => the latest in-effect module refining method_name for it}`, or nil when none is in effect.
117
+ def targets(refinements, method_name, list)
118
+ out = nil
119
+ refinements.each do |refined, methods|
120
+ modules = methods[method_name]
121
+ next if modules.nil?
122
+
123
+ best = latest(modules, list)
124
+ (out ||= {})[refined] = best if best
125
+ end
126
+ out
127
+ end
128
+
129
+ # The refinement table records every name a `refine` argument's spelling can denote (`refine String` inside
130
+ # `module M` records `M::String` and `String`), which only withholds a check. To type, a key must be the class
131
+ # Ruby's lexical lookup resolves: one the project or RBS knows, with no longer spelling of the same last segment
132
+ # that the same module refines the name for and that is known too (that one is innermost, so it wins). Nil
133
+ # when no key survives.
134
+ def resolved(scope, targets, method_name)
135
+ return nil if targets.nil?
136
+
137
+ refinements = scope.discovered_refinements
138
+ kept = targets.select do |refined, module_name|
139
+ known_class?(scope, refined) && !shadowed?(scope, refinements, refined, module_name, method_name)
140
+ end
141
+ kept.empty? ? nil : kept
142
+ end
143
+
144
+ def shadowed?(scope, refinements, refined, module_name, method_name)
145
+ suffix = "::#{refined.split('::').last}"
146
+ depth = refined.count(":")
147
+ refinements.any? do |other, methods|
148
+ other.count(":") > depth && other.end_with?(suffix) && methods[method_name]&.include?(module_name) &&
149
+ known_class?(scope, other)
150
+ end
151
+ end
152
+
153
+ def known_class?(scope, name)
154
+ scope.discovered_classes.key?(name) || scope.environment&.class_known?(name) || false
155
+ end
156
+
157
+ def latest(modules, list)
158
+ best = nil
159
+ best_index = -1
160
+ modules.each do |module_name|
161
+ index = list.index(module_name)
162
+ next unless index && index > best_index
163
+
164
+ best = module_name
165
+ best_index = index
166
+ end
167
+ best
168
+ end
169
+
170
+ # ADR-121 WD7 — a names-wildcard row counts as a row for every name, and a row whose class the walk could not
171
+ # name reaches every level.
172
+ def unknown_reaches?(scope, refinements, method_name, levels)
173
+ unread_class = refinements.any? do |refined, methods|
174
+ (methods.key?(method_name) || methods.key?(wildcard)) &&
175
+ InEffectRefinements.class_wildcard_key?(scope, refined)
176
+ end
177
+ return true if unread_class
178
+
179
+ levels.any? do |_level_class, entries|
180
+ entries.any? do |entry|
181
+ methods = refinements[entry]
182
+ !methods.nil? && (methods.key?(method_name) || methods.key?(wildcard))
183
+ end
184
+ end
185
+ end
186
+
187
+ # A level the project mixes a module into whose position RBS does not record (`mixed`) is decided by its own
188
+ # refinement first, as any level is; past that, the mixin may define the name, so a refinement further up the
189
+ # walk may or may not win and the answer is {UNKNOWN}, never the replaced method's. ADR-121 WD7: a level an
190
+ # in-effect module has a names-wildcard row for (`unread`) may define the name too, so it answers {UNKNOWN}.
191
+ #
192
+ # Issue #1752 — on a class object's walk a singleton level that only an entry no table names may answer
193
+ # (`:continue_unsure`) makes a refinement reached past it {UNKNOWN}: that entry may define the name.
194
+ def walk(scope, levels, method_name, targets, unread)
195
+ unsure = false
196
+ levels.each_index do |index|
197
+ answer = level_answer(scope, levels, index, method_name, [targets, unread])
198
+ next unsure = true if answer == :continue_unsure
199
+ next if answer == :continue
200
+
201
+ return unsure && answer.is_a?(Winner) ? UNKNOWN : answer
202
+ end
203
+ nil
204
+ end
205
+
206
+ def level_answer(scope, levels, index, method_name, (targets, unread))
207
+ level_class, entries, mixed, every = levels[index]
208
+ return UNKNOWN if level_class && unread.key?(level_class)
209
+
210
+ refining = level_class && targets[level_class]
211
+ return Winner.new(module_name: refining, refined_class: level_class) if refining
212
+ return targeted_after?(levels, index, targets, unread) ? UNKNOWN : nil if mixed
213
+
214
+ walk_level(scope, [entries, every], level_class, method_name, [targets, unread])
215
+ end
216
+
217
+ def walk_level(scope, (entries, every), level_class, method_name, (targets, unread))
218
+ if level_class.is_a?(SingletonClass)
219
+ return ClassObject.walk_level(scope, every || entries, method_name, targets, unread)
220
+ end
221
+
222
+ walk_entries(scope, entries, level_class, method_name, targets, unread)
223
+ end
224
+
225
+ # A level's prepended modules, its own method and its included modules, in that order: `:continue` when none
226
+ # answers, so the walk moves on.
227
+ def walk_entries(scope, entries, level_class, method_name, targets, unread)
228
+ entries.each do |entry|
229
+ unless entry == level_class
230
+ return UNKNOWN if unread.key?(entry)
231
+
232
+ refining = targets[entry]
233
+ return Winner.new(module_name: refining, refined_class: entry) if refining
234
+ end
235
+ return nil if defines?(scope, entry, method_name)
236
+ end
237
+ :continue
238
+ end
239
+
240
+ def targeted_after?(levels, index, targets, unread)
241
+ levels.drop(index).any? do |_level_class, entries|
242
+ entries.any? { |entry| targets.key?(entry) || unread.key?(entry) }
243
+ end
244
+ end
245
+
246
+ # `[[level class, [entries in lookup order], mixed, every entry], …]` for an instance of `class_name`, or nil
247
+ # when the walk cannot be trusted: a chain cut at its limit, or one that records a mixin the tables cannot
248
+ # name. `every entry` (project levels only) is the level's entries in lookup order with each one that
249
+ # neither the project's resolver nor RBS can name kept as its candidate names (an Array) where `entries`
250
+ # leaves it out: the typer never reads it, and {OwnMethod} declines where a refinement may name one.
251
+ # `kind: :singleton` answers the class object `class_name` instead ({ClassObject.levels}, issue #1752).
252
+ def levels(scope, class_name, kind: :instance)
253
+ return ClassObject.levels(scope, class_name) if kind == :singleton
254
+
255
+ environment = scope.environment
256
+ return rbs_levels(scope, class_name) if environment&.class_known?(class_name)
257
+
258
+ project_levels(scope, class_name)
259
+ end
260
+
261
+ # The RBS ancestors, each a level of its own, `mixed` where the project reopens one to mix a module in (`class
262
+ # String; include Loud; end`), whose position among them RBS does not record. Each ancestor's name is a
263
+ # dependency, so a file that adds such a reopening re-checks the consumer.
264
+ def rbs_levels(scope, class_name)
265
+ loader = scope.environment.rbs_loader
266
+ names = loader ? loader.ancestor_names_for(class_name) : []
267
+ names = [class_name] if names.empty?
268
+ names.map { |name| [name, [name], project_mixin?(scope, name)] }
269
+ end
270
+
271
+ def project_mixin?(scope, name)
272
+ Analysis::DependencyRecorder.read_last_segment(:class, name) if Analysis::DependencyRecorder.active?
273
+ !scope.includes_of(name).empty? || scope.discovery.discovered_prepends.key?(name) ||
274
+ (scope.discovered_classes.key?(name) &&
275
+ Scope::ResolutionChain.for(scope, name, :instance, :methods).wildcard_mixin?)
276
+ end
277
+
278
+ def project_levels(scope, class_name)
279
+ chain = Scope::ResolutionChain.for(scope, class_name, :instance, :methods)
280
+ chain.record(scope) if Analysis::DependencyRecorder.active?
281
+ return nil if chain.truncated? || chain.wildcard_mixin?
282
+
283
+ out = []
284
+ chain.level_count.times do |index|
285
+ level_class = chain.level_classes[index]
286
+ every = chain.level_entries(index).map { |entry| entry_name(scope, entry) || unnamed(entry) }
287
+ names = every.grep(String)
288
+ next out << [level_class, names, false, every] if level_class
289
+
290
+ names.each { |name| out.concat(rbs_levels(scope, name)) }
291
+ end
292
+ out
293
+ end
294
+
295
+ def unnamed(entry) = entry.candidates.empty? ? [entry.raw.to_s] : entry.candidates
296
+
297
+ def entry_name(scope, entry)
298
+ return entry.name unless entry.external?
299
+
300
+ environment = scope.environment
301
+ entry.candidates.find { |candidate| environment&.class_known?(candidate) }
302
+ end
303
+
304
+ # Does `name` define `method_name` on its instance side itself: a project `def` (its dependency edge recorded
305
+ # through `Scope#user_def_for`), another project definer (`attr_*`, `define_method`), or an RBS declaration
306
+ # written on `name` rather than on an ancestor? A {SingletonClass} asks its own singleton methods.
307
+ def defines?(scope, name, method_name)
308
+ return ClassObject.defines?(scope, name.of, method_name) if name.is_a?(SingletonClass)
309
+ return true if scope.user_def_for(name, method_name)
310
+ return true if scope.discovered_method?(name, method_name, :instance)
311
+
312
+ definition = ExternalAncestorResolution.method_definition(name, method_name, :instance, scope: scope)
313
+ ExternalAncestorResolution.declared_on_class?(definition, name)
314
+ end
315
+ end
316
+ end
317
+ end
318
+
319
+ require_relative "refined_dispatch/class_object"
320
+ require_relative "refined_dispatch/own_method"
@@ -20,6 +20,8 @@ module Rigor
20
20
  # `cache = {}; [1, 2].find { |e| (cache[:first] ||= e) == 2 }` answered its own `2`, where Ruby keeps the
21
21
  # first iteration's `1`, folded `find` to `2` and reported `found == 2` always-truthy. The pass marks the
22
22
  # sites this module returns (`Scope#with_repeated_or_writes`), and the reading is withheld at a marked site.
23
+ # `StatementEvaluator` types a repeating body from one entry scope too — its statement pass and every ADR-56
24
+ # write-back pass — and marks the generic pass's sites there (issue #1336).
23
25
  #
24
26
  # The mark is per site, not per receiver, because nothing about the receiver's type can say that its slot
25
27
  # was filled: `{}` widens to `Hash[Dynamic[top], Dynamic[top]]`, a bare `Hash.new` is not widened at all, and
@@ -47,6 +47,19 @@ module Rigor
47
47
  @unpositioned = nil
48
48
  @hook_params = nil
49
49
  @body_blocks = nil
50
+ @program = nil
51
+ end
52
+
53
+ # Marks the file's own top-level statements: a receiverless `include` among them is `main.include`,
54
+ # which mixes into `Object` (issue #1697).
55
+ def program_body(body)
56
+ return unless body.is_a?(Prism::StatementsNode)
57
+
58
+ @program = Set.new.compare_by_identity.merge(body.body)
59
+ end
60
+
61
+ def program_statement?(node)
62
+ !@program.nil? && @program.include?(node)
50
63
  end
51
64
 
52
65
  # Marks the statements of a body whose own position is a fact — the calls and declarations in them