rigortype 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) 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 +6 -0
  25. data/docs/handbook/appendix-liskov.md +5 -0
  26. data/docs/manual/03-configuration.md +1 -1
  27. data/docs/manual/04-diagnostics.md +2 -1
  28. data/docs/manual/06-baseline.md +2 -0
  29. data/docs/manual/07-plugins.md +1 -1
  30. data/docs/manual/18-removing-dead-code.md +32 -2
  31. data/docs/manual/plugins/README.md +6 -1
  32. data/docs/manual/plugins/rigor-activerecord.md +21 -0
  33. data/docs/manual/plugins/rigor-alba.md +60 -0
  34. data/docs/manual/plugins/rigor-minitest.md +22 -4
  35. data/docs/manual/plugins/rigor-typelizer.md +71 -0
  36. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
  37. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
  38. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
  39. data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
  40. data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
  41. data/lib/rigor/analysis/check_rules.rb +289 -57
  42. data/lib/rigor/analysis/dependency_recorder.rb +15 -0
  43. data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
  44. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
  45. data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
  46. data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
  47. data/lib/rigor/analysis/erb_template_detector.rb +2 -1
  48. data/lib/rigor/analysis/incremental.rb +29 -9
  49. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
  50. data/lib/rigor/analysis/incremental_run_slot.rb +3 -1
  51. data/lib/rigor/analysis/incremental_session.rb +14 -1
  52. data/lib/rigor/analysis/reachability/graph.rb +123 -23
  53. data/lib/rigor/analysis/reachability/scan.rb +290 -36
  54. data/lib/rigor/analysis/rule_catalog.rb +43 -0
  55. data/lib/rigor/analysis/run_cache_key.rb +16 -1
  56. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
  57. data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
  58. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  59. data/lib/rigor/analysis/runner.rb +34 -3
  60. data/lib/rigor/analysis/worker_session.rb +7 -4
  61. data/lib/rigor/cache/incremental_snapshot.rb +22 -0
  62. data/lib/rigor/cli/annotate_command.rb +5 -0
  63. data/lib/rigor/cli/baseline_command.rb +7 -18
  64. data/lib/rigor/cli/check_command.rb +2 -1
  65. data/lib/rigor/cli/trace_command.rb +5 -0
  66. data/lib/rigor/cli/type_of_command.rb +5 -3
  67. data/lib/rigor/cli/unused_command.rb +13 -7
  68. data/lib/rigor/cli.rb +10 -2
  69. data/lib/rigor/configuration/severity_profile.rb +6 -0
  70. data/lib/rigor/configuration.rb +46 -2
  71. data/lib/rigor/environment/gated_signature_guard.rb +43 -0
  72. data/lib/rigor/environment/gated_signature_plan.rb +339 -0
  73. data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
  74. data/lib/rigor/environment/rbs_loader.rb +283 -14
  75. data/lib/rigor/environment/required_features.rb +206 -0
  76. data/lib/rigor/environment.rb +21 -3
  77. data/lib/rigor/inference/acceptance.rb +28 -3
  78. data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
  79. data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
  80. data/lib/rigor/inference/def_node_resolver.rb +23 -1
  81. data/lib/rigor/inference/expression_typer.rb +215 -16
  82. data/lib/rigor/inference/global_write_census.rb +7 -4
  83. data/lib/rigor/inference/guard_rebinding.rb +1 -1
  84. data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
  85. data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
  86. data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
  87. data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
  88. data/lib/rigor/inference/in_effect_refinements.rb +256 -0
  89. data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
  90. data/lib/rigor/inference/key_presence_guard.rb +303 -0
  91. data/lib/rigor/inference/macro_block_self_type.rb +59 -12
  92. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  93. data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
  94. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
  95. data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
  96. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
  97. data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
  98. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
  99. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
  100. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +259 -28
  101. data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
  102. data/lib/rigor/inference/method_dispatcher.rb +44 -11
  103. data/lib/rigor/inference/module_function_state.rb +2 -1
  104. data/lib/rigor/inference/narrowing.rb +20 -6
  105. data/lib/rigor/inference/object_mixins.rb +180 -0
  106. data/lib/rigor/inference/project_patched_methods.rb +10 -5
  107. data/lib/rigor/inference/project_patched_scanner.rb +71 -3
  108. data/lib/rigor/inference/refine_census.rb +239 -0
  109. data/lib/rigor/inference/refine_self.rb +317 -0
  110. data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
  111. data/lib/rigor/inference/refined_dispatch.rb +272 -0
  112. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
  113. data/lib/rigor/inference/scope_indexer.rb +687 -60
  114. data/lib/rigor/inference/statement_evaluator.rb +71 -20
  115. data/lib/rigor/inference/string_mutation.rb +9 -3
  116. data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
  117. data/lib/rigor/inference/version_guard.rb +19 -10
  118. data/lib/rigor/language_server/project_context.rb +31 -0
  119. data/lib/rigor/language_server/server.rb +18 -0
  120. data/lib/rigor/plugin/inflector.rb +1 -1
  121. data/lib/rigor/plugin/io_boundary.rb +42 -3
  122. data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
  123. data/lib/rigor/project_environment.rb +29 -6
  124. data/lib/rigor/protection/mutation_cache.rb +1 -1
  125. data/lib/rigor/protection/mutator.rb +1 -1
  126. data/lib/rigor/reflection/constant_ancestors.rb +127 -11
  127. data/lib/rigor/reflection/constant_path.rb +13 -2
  128. data/lib/rigor/reflection.rb +40 -8
  129. data/lib/rigor/scope/discovery_index.rb +40 -1
  130. data/lib/rigor/scope/resolution_chain.rb +159 -11
  131. data/lib/rigor/scope.rb +189 -10
  132. data/lib/rigor/sig_gen/classification.rb +4 -1
  133. data/lib/rigor/sig_gen/generator.rb +55 -5
  134. data/lib/rigor/sig_gen/method_candidate.rb +2 -2
  135. data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
  136. data/lib/rigor/source/alias_names.rb +52 -0
  137. data/lib/rigor/source/literals.rb +3 -2
  138. data/lib/rigor/source/symbol_name.rb +20 -0
  139. data/lib/rigor/type/combinator.rb +34 -2
  140. data/lib/rigor/type/float_range.rb +4 -1
  141. data/lib/rigor/type/nominal.rb +6 -1
  142. data/lib/rigor/version.rb +1 -1
  143. data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
  144. data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
  145. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
  146. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
  147. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
  148. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
  149. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
  150. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
  151. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
  152. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
  153. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
  154. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
  155. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
  156. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
  157. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
  158. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
  159. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
  160. data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
  161. data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
  162. data/plugins/rigor-alba/sig/alba.rbs +4 -0
  163. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
  164. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
  165. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
  166. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
  167. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
  168. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
  169. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
  170. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
  171. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
  172. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
  173. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
  174. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
  175. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
  176. data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
  177. data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
  178. data/sig/rigor/plugin/io_boundary.rbs +3 -0
  179. data/sig/rigor/scope.rbs +27 -4
  180. data/sig/rigor/source.rbs +4 -0
  181. data/sig/rigor.rbs +3 -1
  182. metadata +66 -1
@@ -0,0 +1,317 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../source/constant_path"
6
+ require_relative "refine_census"
7
+
8
+ module Rigor
9
+ module Inference
10
+ # ADR-121 WD7 — the one function that answers what `self` a `refine` (or a `:refine` literal) at a program point
11
+ # runs on. The in-effect walk ({InEffectRefinements}) works a {Context} out with these functions for every body,
12
+ # method and block it enters and records it for each `refine`-shaped node; the census (`ScopeIndexer`'s module-body
13
+ # walk and its scan of method and refine bodies) reads that record back ({InEffectRefinements#refine_context}), so
14
+ # the table and the activations charge the same module.
15
+ module RefineSelf
16
+ # `here`, for a `refine` call directly at that point; `literal`, for a `:refine` literal there (which names
17
+ # `Module#refine` for an alias or a `send` whose method runs where a method defined there does: in a module's
18
+ # `class << self` body that is the module, though a `refine` call there runs on the singleton class); and
19
+ # `instance` / `singleton`, for a method defined there on the instance / singleton side. Each is a module name,
20
+ # the names a constant's spelling can denote (an Array, innermost first, for a module only another file may
21
+ # declare), the wildcard (a module the walk cannot name), {CLASS}, {MAIN}, {ANONYMOUS} or {REFINEMENT}.
22
+ # `deferred` is true inside a method body (a `def`, a `define_method` block), where a `refine` runs only once the
23
+ # method is called.
24
+ # `own_method` is true inside a singleton `def` of a module the walk names ({.def_body}), and in the blocks that
25
+ # keep its `self`: `here` is then that module itself, so a `refine X do … end` written there refines X for it
26
+ # once the method is called, and the census records its rows where it would otherwise charge a wildcard.
27
+ # `remote` is true in the body of an eval on a constant (`M.module_eval { … }`, and a `class << self` there),
28
+ # whose file need not declare the module it runs on: a singleton method defined there is not `own_method`.
29
+ Context = Data.define(:here, :literal, :instance, :singleton, :deferred, :own_method, :remote)
30
+ # A plain class, or an instance of one: `Class` undefines `refine`, so a `refine` there is the class's own
31
+ # method (#1689) and a `:refine` literal is data.
32
+ CLASS = :class
33
+ # The top level's `main`, which has no `refine`.
34
+ MAIN = :main
35
+ # A module a bare `Module.new { … }` creates, which the census names by its position.
36
+ ANONYMOUS = :anonymous
37
+ # The refinement a `refine` block creates, where a method defined on its singleton side (`def self.x`, a
38
+ # `define_singleton_method` block, a `class << self` body) runs. `using` rejects a refinement with `TypeError`,
39
+ # so a `refine` there reaches no `using` span and the census charges it to nothing.
40
+ REFINEMENT = :refinement
41
+
42
+ # Core iteration methods: the Enumerable protocol never rebinds `self` in their block.
43
+ CORE_ITERATORS = Set[
44
+ :each, :each_with_index, :each_with_object, :each_pair, :each_key, :each_value, :map, :flat_map, :times,
45
+ :select, :reject
46
+ ].freeze
47
+ LITERALS = [Prism::ArrayNode, Prism::HashNode, Prism::RangeNode, Prism::IntegerNode, Prism::FloatNode,
48
+ Prism::StringNode, Prism::InterpolatedStringNode, Prism::SymbolNode].freeze
49
+ NAMED_RECEIVERS = [Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::LocalVariableReadNode,
50
+ Prism::InstanceVariableReadNode].freeze
51
+ METHOD_BLOCK_CALLS = Set[:define_method, :define_singleton_method].freeze
52
+ INSTANCE_EVAL_CALLS = Set[:instance_eval, :instance_exec].freeze
53
+ # Bindings of a local or an instance variable that are not a plain write: each may hold any value.
54
+ VARIABLE_BINDINGS = [
55
+ Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
56
+ Prism::LocalVariableTargetNode, Prism::RequiredParameterNode, Prism::OptionalParameterNode,
57
+ Prism::RestParameterNode, Prism::KeywordRestParameterNode, Prism::RequiredKeywordParameterNode,
58
+ Prism::OptionalKeywordParameterNode, Prism::BlockParameterNode, Prism::BlockLocalVariableNode,
59
+ Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
60
+ Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableTargetNode
61
+ ].freeze
62
+ # Bindings of a constant other than a bare `K = …`, matched by the constant's last segment.
63
+ CONSTANT_BINDINGS = [
64
+ Prism::ConstantOrWriteNode, Prism::ConstantOperatorWriteNode, Prism::ConstantAndWriteNode,
65
+ Prism::ConstantTargetNode, Prism::ConstantPathTargetNode
66
+ ].freeze
67
+ CONSTANT_PATH_BINDINGS = [
68
+ Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
69
+ Prism::ConstantPathAndWriteNode
70
+ ].freeze
71
+
72
+ private_constant :CORE_ITERATORS, :LITERALS, :NAMED_RECEIVERS, :METHOD_BLOCK_CALLS, :INSTANCE_EVAL_CALLS,
73
+ :VARIABLE_BINDINGS, :CONSTANT_BINDINGS, :CONSTANT_PATH_BINDINGS
74
+
75
+ module_function
76
+
77
+ def wildcard = RefineCensus.wildcard
78
+
79
+ # The top level: `main`; a method defined there is `Object`'s, which any module may run.
80
+ def top = context(MAIN, wildcard, MAIN)
81
+
82
+ # A body whose `self` the walk cannot name.
83
+ def unknown = context(wildcard, wildcard, wildcard)
84
+
85
+ def context(here, instance, singleton, deferred: false, literal: here, own_method: false, remote: false)
86
+ Context.new(here: here, literal: literal, instance: instance, singleton: singleton, deferred: deferred,
87
+ own_method: own_method, remote: remote)
88
+ end
89
+
90
+ # A `module` body (or a `Module.new` block) of the module `name` (nil when the walk cannot name it, or
91
+ # {ANONYMOUS}): its instance methods run on whatever includes or extends it, its singleton methods on it.
92
+ def module_body(name)
93
+ return unknown if name.nil?
94
+
95
+ context(name, wildcard, name)
96
+ end
97
+
98
+ # A `class` body (or a `Class.new` / `Struct.new` / `Data.define` block): `self` is a class; a `Module`
99
+ # subclass's instances are modules the walk cannot name.
100
+ def class_body(module_subclass) = context(CLASS, module_subclass ? wildcard : CLASS, CLASS)
101
+
102
+ # A `class << expr` body: `self` is a singleton class, so a `refine` call there is its own method; for
103
+ # `class << self` its methods (and a `:refine` literal an alias there names) run on the enclosing `self`, for any
104
+ # other expression on an object the walk cannot name.
105
+ def singleton_class_body(parent, of_self)
106
+ runs_on = of_self ? parent.singleton : wildcard
107
+ context(CLASS, runs_on, CLASS, literal: runs_on, remote: of_self && parent.remote)
108
+ end
109
+
110
+ # A method body defined in `parent` on the instance or singleton side: `self` is what that side runs on, and a
111
+ # method defined inside it is defined on something the walk cannot name.
112
+ def method_body(parent, singleton)
113
+ context(singleton ? parent.singleton : parent.instance, wildcard, wildcard, deferred: true)
114
+ end
115
+
116
+ # A `def` body in `parent`: on no receiver an instance method, on `self` (or on the constant that is the last
117
+ # segment of the module `parent` names, `def M.x` inside `module M`) a singleton one, and on any other object a
118
+ # method defined on something the walk cannot name. A singleton method of a module the walk names is the
119
+ # module's own (`own_method`): `def self.x`, or a `def` written in that module's `class << self`
120
+ # (`parent.instance` is the module there). `self` is then the module itself, whichever way the method is called,
121
+ # so a `refine` in it refines for that module; for an instance `def`, whose `parent.instance` is the wildcard,
122
+ # Ruby runs it on whatever module extends the owner. `def M.x` is not marked, as the constant names the module by
123
+ # its last segment only, and neither is a method an eval on a constant defines (`remote`): its file need not
124
+ # declare the module, so the lookup of a refine body, which consults the files that do, would not see it.
125
+ def def_body(parent, receiver)
126
+ return mark_own_method(method_body(parent, false), parent) if receiver.nil?
127
+ return unknown.with(deferred: true) unless lexical_self_receiver?(receiver, parent)
128
+
129
+ context = method_body(parent, true)
130
+ receiver.is_a?(Prism::SelfNode) ? mark_own_method(context, parent) : context
131
+ end
132
+
133
+ def mark_own_method(context, parent)
134
+ named_module?(context.here) && !parent.remote ? context.with(own_method: true) : context
135
+ end
136
+
137
+ def lexical_self_receiver?(receiver, context)
138
+ return true if receiver.is_a?(Prism::SelfNode)
139
+
140
+ here = context.here
141
+ receiver.is_a?(Prism::ConstantReadNode) && here.is_a?(String) && here.split("::").last == receiver.name.to_s
142
+ end
143
+
144
+ # A module name the walk can charge a `refine` to: not the wildcard, {CLASS}, {MAIN} or {ANONYMOUS}, and not the
145
+ # several names a constant's spelling can denote.
146
+ def named_module?(here) = here.is_a?(String) && here != wildcard
147
+
148
+ # The context inside the block of `node` (a block-carrying call, or a lambda) written in `parent`. `eval_names`
149
+ # are the modules a `*_eval` / `*_exec` call's constant receiver may name ({.eval_receiver_names}), or nil; the
150
+ # given block answers whether a constant, local or instance variable receiver holds a literal ({.keeps_self?}).
151
+ # A block keeps `self` only where its call is known to: a core iteration method on a receiver whose value is a
152
+ # literal in this file, and a `refine` call (its body is the refinement, handled by the reader). A `*_eval` /
153
+ # `*_exec` block runs on its receiver, a `define_method` / `define_singleton_method` block is a method body, and
154
+ # a `Module.new` / `Class.new` block is a module / class body. Any other block (a DSL may `module_eval` it,
155
+ # `tap`, `loop`, a project method, a core method's name on any other receiver), a lambda, and every block at the
156
+ # top level may run under any `self`.
157
+ def block(parent, node, eval_names, &)
158
+ return unknown if node.is_a?(Prism::LambdaNode)
159
+ return refinement_body(parent) if node.name == :refine
160
+ return eval_body(parent, node, eval_names) if RefineCensus::SELF_EVAL_CALLS.include?(node.name)
161
+ return meta_new_body(node) if ScopeIndexer.meta_new_constant_rvalue?(node)
162
+ return unknown if parent.here == MAIN
163
+ return method_body(parent, node.name == :define_singleton_method) if method_block?(node)
164
+ return parent if keeps_self?(node, &)
165
+
166
+ unknown
167
+ end
168
+
169
+ def method_block?(node) = METHOD_BLOCK_CALLS.include?(node.name) && RefineCensus.self_call?(node)
170
+
171
+ # The block of a `refine` call: a `refine` written in it (a nested one) is charged where the call is, but `self`
172
+ # is the refinement, so a method defined in it is defined on something the walk cannot name: a singleton method
173
+ # on the refinement itself ({REFINEMENT}, whichever the method the `refine` call sits in, as long as the module it
174
+ # refines for is one), an instance method on an instance of the refined class (the wildcard), not on the module
175
+ # around.
176
+ def refinement_body(parent)
177
+ singleton = module_here?(parent.here) ? REFINEMENT : parent.singleton
178
+ parent.with(instance: refinement_side(parent.instance), singleton: singleton, own_method: false)
179
+ end
180
+
181
+ def refinement_side(side) = side.is_a?(String) || side.is_a?(Array) ? wildcard : side
182
+
183
+ # Is `here` a module a `refine` there refines for (so its block is a refinement's body), or the refinement itself?
184
+ def module_here?(here) = named_module?(here) || here.is_a?(Array) || here == ANONYMOUS || here == REFINEMENT
185
+
186
+ def meta_new_body(node)
187
+ ScopeIndexer.module_new_call?(node) ? module_body(ANONYMOUS) : class_body(false)
188
+ end
189
+
190
+ # A core iteration method on a receiver whose value is a literal in this file: a literal, or a constant, local
191
+ # or instance variable the block answers true for ({.literal_bindings}). Any other name, even on a literal, and
192
+ # any other receiver may be a project method that runs the block under another `self`: on Ruby 4.0.5
193
+ # `Config.each { refine(String) { … } }` with `def self.each(&blk) = Ext.module_eval(&blk)` refines for `Ext`,
194
+ # and so does `[1].configure { … }` with `class Array; def configure(&blk) = Ext.module_eval(&blk); end`.
195
+ def keeps_self?(node)
196
+ return false unless CORE_ITERATORS.include?(node.name)
197
+
198
+ receiver = node.receiver
199
+ return true if literal?(receiver)
200
+
201
+ NAMED_RECEIVERS.any? { |klass| receiver.is_a?(klass) } && block_given? && yield(receiver)
202
+ end
203
+
204
+ def literal?(node) = LITERALS.any? { |klass| node.is_a?(klass) }
205
+
206
+ # A literal, or a literal's `.freeze`.
207
+ def literal_value?(node)
208
+ return true if literal?(node)
209
+
210
+ node.is_a?(Prism::CallNode) && node.name == :freeze && node.arguments.nil? && node.block.nil? &&
211
+ literal?(node.receiver)
212
+ end
213
+
214
+ # The constants (by qualified name), locals and instance variables (by name) that `root` binds, every binding a
215
+ # plain write of a literal ({.literal_value?}). A name any other binding gives a value (a parameter, a
216
+ # multiple assignment, `||=`, `+=`, a `const_set`, a path write `A::K = …`) is not one, wherever in the file
217
+ # it sits.
218
+ def literal_bindings(root)
219
+ literal = Set.new
220
+ other = Set.new
221
+ spoiled = ScopeIndexer.loose_constant_segments(root).dup
222
+ collect_bindings(root, [], literal, other, spoiled)
223
+ (literal - other).reject { |key| key.is_a?(String) && spoiled.include?(key.split("::").last) }.to_set.freeze
224
+ end
225
+
226
+ def collect_bindings(node, prefix, literal, other, spoiled)
227
+ case node
228
+ when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode
229
+ return collect_declaration_bindings(node, prefix, literal, other, spoiled)
230
+ when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode
231
+ bind(node.value, node.name, literal, other)
232
+ when *VARIABLE_BINDINGS then other << node.name if node.name
233
+ else collect_constant_binding(node, prefix, literal, other, spoiled)
234
+ end
235
+ node.rigor_each_child { |child| collect_bindings(child, prefix, literal, other, spoiled) }
236
+ end
237
+
238
+ def collect_constant_binding(node, prefix, literal, other, spoiled)
239
+ case node
240
+ when Prism::ConstantWriteNode then bind(node.value, (prefix + [node.name.to_s]).join("::"), literal, other)
241
+ when *CONSTANT_BINDINGS then spoiled << node.name.to_s
242
+ when *CONSTANT_PATH_BINDINGS then spoiled << node.target.name.to_s
243
+ end
244
+ end
245
+
246
+ # A `class` / `module` body's constants are its own; a `class << expr` body's are the singleton class's, which
247
+ # no read outside it names.
248
+ def collect_declaration_bindings(node, prefix, literal, other, spoiled)
249
+ if node.is_a?(Prism::SingletonClassNode)
250
+ collect_bindings(node.expression, prefix, literal, other, spoiled)
251
+ inner = prefix + ["<singleton>"]
252
+ else
253
+ inner = Source::ConstantPath.declaration_prefix(prefix, node.constant_path) || prefix
254
+ superclass = node.is_a?(Prism::ClassNode) && node.superclass
255
+ collect_bindings(superclass, prefix, literal, other, spoiled) if superclass
256
+ end
257
+ collect_bindings(node.body, inner, literal, other, spoiled) if node.body
258
+ end
259
+
260
+ def bind(value, key, literal, other) = (literal_value?(value) ? literal : other) << key
261
+
262
+ # The key {.literal_bindings} holds for a receiver read in `prefix`: a constant's innermost lexical candidate (the
263
+ # one Ruby's lookup finds once this file's write defines it), a local's or an instance variable's name.
264
+ def binding_key(receiver, prefix)
265
+ case receiver
266
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
267
+ ScopeIndexer.constant_receiver_candidates(receiver, prefix).first
268
+ else receiver.name
269
+ end
270
+ end
271
+
272
+ # A `*_eval` / `*_exec` block: on `self` (or no receiver) it keeps `self`, on a constant it runs on the module the
273
+ # constant names (any name its spelling can denote), on anything else on a module the walk cannot name. A `def` in
274
+ # an `instance_eval` binds on the receiver's singleton, so its instance side is the receiver itself.
275
+ def eval_body(parent, node, eval_names)
276
+ here = RefineCensus.self_call?(node) ? parent.here : eval_names
277
+ here = here.first if here.is_a?(Array) && here.size == 1
278
+ return context(CLASS, CLASS, CLASS, deferred: parent.deferred) if here == CLASS
279
+ return unknown unless here.is_a?(String) || here.is_a?(Array) || here == ANONYMOUS || here == REFINEMENT
280
+ return unknown if here == wildcard
281
+
282
+ instance = INSTANCE_EVAL_CALLS.include?(node.name) ? here : wildcard
283
+ remote = parent.remote || !RefineCensus.self_call?(node)
284
+ context(here, instance, here, deferred: parent.deferred, remote: remote)
285
+ end
286
+
287
+ # The modules a `*_eval` / `*_exec` call `node` written in `context` at the lexical `prefix` may run its block
288
+ # on, when its receiver is a constant: every name the constant's spelling can denote, innermost first, as another
289
+ # file may declare any of them (`M.module_eval { … }` in `module L` runs on `L::M` where some file declares it);
290
+ # a `self::`-anchored one as the method walk resolves it (`ScopeIndexer.eval_receiver_prefix`). Else nil.
291
+ def eval_receiver_names(node, prefix, context)
292
+ return nil unless RefineCensus::SELF_EVAL_CALLS.include?(node.name) && !RefineCensus.self_call?(node)
293
+
294
+ receiver = node.receiver
295
+ if Source::ConstantPath.qualified_name_or_nil(receiver)
296
+ return ScopeIndexer.constant_receiver_candidates(receiver, prefix)
297
+ end
298
+
299
+ here = context.here
300
+ self_prefix = here.is_a?(String) && here != wildcard ? here.split("::") : []
301
+ resolved = ScopeIndexer.eval_receiver_prefix(node, self_prefix, prefix, unnameable_self: self_prefix.empty?)
302
+ [resolved.join("::")] unless resolved.nil? || resolved.empty?
303
+ end
304
+
305
+ # The modules a context's `refine` call (or, with `literal`, its `:refine` literal) may refine for, as names a
306
+ # table row can hold: the module, each name a constant's spelling can denote, else the wildcard.
307
+ def charged_modules(context, literal: false)
308
+ here = literal ? context.literal : context.here
309
+ case here
310
+ when String then [here]
311
+ when Array then here
312
+ else [wildcard]
313
+ end
314
+ end
315
+ end
316
+ end
317
+ end