rigortype 0.4.7 → 0.4.8

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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +29 -0
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/docs/handbook/01-getting-started.md +1 -1
  8. data/docs/handbook/11-sig-gen.md +35 -0
  9. data/docs/install.md +20 -0
  10. data/docs/manual/01-installation.md +67 -26
  11. data/docs/manual/02-cli-reference.md +307 -91
  12. data/docs/manual/03-configuration.md +41 -3
  13. data/docs/manual/04-diagnostics.md +5 -4
  14. data/docs/manual/08-skills.md +11 -0
  15. data/docs/manual/09-editor-integration.md +8 -0
  16. data/docs/manual/14-rails-quickstart.md +1 -1
  17. data/docs/manual/19-effect-labels.md +3 -4
  18. data/docs/manual/plugins/rigor-actionpack.md +15 -6
  19. data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
  20. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  21. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  22. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  23. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
  24. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
  25. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
  26. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  27. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
  28. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  29. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  30. data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
  31. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  32. data/lib/rigor/analysis/check_rules.rb +598 -99
  33. data/lib/rigor/analysis/crash_signature.rb +43 -1
  34. data/lib/rigor/analysis/dependency_recorder.rb +233 -76
  35. data/lib/rigor/analysis/incremental.rb +11 -1
  36. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
  37. data/lib/rigor/analysis/incremental_session.rb +279 -37
  38. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  39. data/lib/rigor/analysis/rule_catalog.rb +37 -1
  40. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  41. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  42. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
  43. data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
  44. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
  45. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
  46. data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
  47. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  48. data/lib/rigor/analysis/runner.rb +222 -39
  49. data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
  50. data/lib/rigor/analysis/worker_session.rb +42 -41
  51. data/lib/rigor/bleeding_edge.rb +19 -4
  52. data/lib/rigor/cache/file_digest.rb +39 -31
  53. data/lib/rigor/cache/incremental_snapshot.rb +66 -7
  54. data/lib/rigor/cache/rbs_descriptor.rb +37 -2
  55. data/lib/rigor/cache/rbs_environment.rb +5 -1
  56. data/lib/rigor/cli/baseline_command.rb +15 -2
  57. data/lib/rigor/cli/check_command.rb +4 -2
  58. data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
  59. data/lib/rigor/cli/lens_command.rb +17 -2
  60. data/lib/rigor/cli/sig_gen_command.rb +8 -0
  61. data/lib/rigor/cli/skill_describe.rb +1 -0
  62. data/lib/rigor/cli.rb +5 -2
  63. data/lib/rigor/config_audit.rb +32 -18
  64. data/lib/rigor/configuration/severity_profile.rb +9 -0
  65. data/lib/rigor/configuration.rb +32 -5
  66. data/lib/rigor/effects/collector.rb +53 -21
  67. data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
  68. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  69. data/lib/rigor/environment/default_libraries.rb +17 -0
  70. data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
  71. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  72. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  73. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  74. data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
  75. data/lib/rigor/environment/rbs_loader.rb +312 -63
  76. data/lib/rigor/environment.rb +131 -35
  77. data/lib/rigor/inference/acceptance.rb +50 -15
  78. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  79. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  80. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  81. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  82. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  83. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  84. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  85. data/lib/rigor/inference/block_run_count.rb +9 -2
  86. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  87. data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
  88. data/lib/rigor/inference/captured_locals.rb +2 -0
  89. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  90. data/lib/rigor/inference/clause_widening.rb +1 -1
  91. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  92. data/lib/rigor/inference/construction_escape.rb +56 -15
  93. data/lib/rigor/inference/declaration_walk.rb +4 -0
  94. data/lib/rigor/inference/def_node_resolver.rb +32 -14
  95. data/lib/rigor/inference/def_return_typer.rb +1 -1
  96. data/lib/rigor/inference/definer_resolution.rb +15 -7
  97. data/lib/rigor/inference/definers_index.rb +72 -16
  98. data/lib/rigor/inference/error_info.rb +16 -0
  99. data/lib/rigor/inference/expression_typer.rb +1459 -287
  100. data/lib/rigor/inference/flow_tracer.rb +31 -14
  101. data/lib/rigor/inference/fork_map.rb +3 -0
  102. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  103. data/lib/rigor/inference/global_write_census.rb +8 -6
  104. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  105. data/lib/rigor/inference/indexed_narrowing.rb +57 -8
  106. data/lib/rigor/inference/ivar_writer_census.rb +106 -11
  107. data/lib/rigor/inference/key_presence_guard.rb +51 -25
  108. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  109. data/lib/rigor/inference/last_line.rb +5 -2
  110. data/lib/rigor/inference/loop_value_record.rb +63 -15
  111. data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
  112. data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
  113. data/lib/rigor/inference/match_rebinding.rb +21 -8
  114. data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
  115. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  116. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  117. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  118. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  119. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  120. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  121. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
  122. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
  123. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
  124. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
  125. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  126. data/lib/rigor/inference/method_dispatcher.rb +66 -18
  127. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  128. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  129. data/lib/rigor/inference/module_function_state.rb +13 -2
  130. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  131. data/lib/rigor/inference/mutation_widening.rb +44 -4
  132. data/lib/rigor/inference/narrowing.rb +201 -27
  133. data/lib/rigor/inference/operand_effects.rb +43 -2
  134. data/lib/rigor/inference/optimistic_origin.rb +133 -8
  135. data/lib/rigor/inference/origin_lookup.rb +1 -1
  136. data/lib/rigor/inference/precision_scanner.rb +1 -1
  137. data/lib/rigor/inference/project_method_ownership.rb +28 -10
  138. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  139. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  140. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  141. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  142. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  143. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  144. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
  145. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
  146. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  147. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  148. data/lib/rigor/inference/remover_holder_scan.rb +76 -22
  149. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  150. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
  151. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
  152. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  153. data/lib/rigor/inference/scope_indexer.rb +2079 -183
  154. data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
  155. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  156. data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
  157. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  158. data/lib/rigor/inference/statement_evaluator.rb +970 -250
  159. data/lib/rigor/inference/string_mutation.rb +14 -0
  160. data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
  161. data/lib/rigor/inference/toplevel_def_census.rb +14 -6
  162. data/lib/rigor/inference/unknown_store_widening.rb +1 -1
  163. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  164. data/lib/rigor/inference/void_tail_summary.rb +29 -10
  165. data/lib/rigor/lens/claims/comparison.rb +78 -0
  166. data/lib/rigor/lens/claims/request.rb +131 -0
  167. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  168. data/lib/rigor/lens/claims.rb +208 -0
  169. data/lib/rigor/lens/contract_reader.rb +5 -0
  170. data/lib/rigor/lens/document.rb +1 -1
  171. data/lib/rigor/lens/file_reader.rb +21 -8
  172. data/lib/rigor/lens/run.rb +8 -0
  173. data/lib/rigor/lens.rb +1 -1
  174. data/lib/rigor/library_probe.rb +68 -0
  175. data/lib/rigor/memo_audit.rb +282 -0
  176. data/lib/rigor/plugin/base.rb +15 -2
  177. data/lib/rigor/plugin/io_boundary.rb +25 -3
  178. data/lib/rigor/plugin/manifest.rb +7 -3
  179. data/lib/rigor/plugin/registry.rb +13 -2
  180. data/lib/rigor/plugin/trust_policy.rb +46 -0
  181. data/lib/rigor/protection/mutation_cache.rb +4 -1
  182. data/lib/rigor/reflection/constant_ancestors.rb +79 -22
  183. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  184. data/lib/rigor/reflection/declarations.rb +58 -0
  185. data/lib/rigor/reflection/project_method.rb +177 -0
  186. data/lib/rigor/reflection.rb +2 -0
  187. data/lib/rigor/rooted_glob.rb +18 -1
  188. data/lib/rigor/run_scoped_slots.rb +20 -20
  189. data/lib/rigor/scope/discovery_index.rb +149 -2
  190. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  191. data/lib/rigor/scope/resolution_chain.rb +900 -55
  192. data/lib/rigor/scope.rb +307 -34
  193. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  194. data/lib/rigor/sig_gen/classification.rb +14 -1
  195. data/lib/rigor/sig_gen/generator.rb +222 -13
  196. data/lib/rigor/sig_gen/method_candidate.rb +14 -0
  197. data/lib/rigor/sig_gen/observation_collector.rb +11 -2
  198. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  199. data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
  200. data/lib/rigor/sig_gen/writer.rb +21 -0
  201. data/lib/rigor/sig_gen.rb +1 -0
  202. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  203. data/lib/rigor/source/node_children.rb +13 -0
  204. data/lib/rigor/type/combinator.rb +164 -8
  205. data/lib/rigor/type/interface.rb +60 -0
  206. data/lib/rigor/type.rb +1 -0
  207. data/lib/rigor/type_of_memo/serving.rb +99 -0
  208. data/lib/rigor/type_of_memo/stats.rb +43 -0
  209. data/lib/rigor/type_of_memo.rb +337 -0
  210. data/lib/rigor/typing_state.rb +1094 -0
  211. data/lib/rigor/version.rb +1 -1
  212. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
  213. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
  214. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  215. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  216. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  217. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  218. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  219. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
  220. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
  221. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  222. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
  223. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  224. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
  225. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
  226. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
  227. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  228. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  229. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
  230. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  231. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
  232. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
  233. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
  234. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
  235. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
  236. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  237. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  238. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
  239. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  240. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
  241. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  242. data/sig/rigor/analysis/fact_store.rbs +2 -3
  243. data/sig/rigor/cache/descriptor.rbs +14 -0
  244. data/sig/rigor/environment.rbs +4 -1
  245. data/sig/rigor/plugin/base.rbs +53 -19
  246. data/sig/rigor/plugin/interfaces.rbs +51 -0
  247. data/sig/rigor/plugin/io_boundary.rbs +2 -3
  248. data/sig/rigor/plugin/node_context.rbs +21 -0
  249. data/sig/rigor/reflection.rbs +37 -0
  250. data/sig/rigor/scope.rbs +22 -3
  251. data/sig/rigor/trinary.rbs +1 -0
  252. data/sig/rigor/type.rbs +9 -3
  253. data/sig/rigor.rbs +3 -2
  254. data/skills/rigor-ask/SKILL.md +1 -0
  255. data/skills/rigor-project-init/references/01-detect.md +1 -0
  256. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
  257. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  258. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  259. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  260. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  261. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  262. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  263. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  264. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  265. data/skills/rigor-type-oracle/SKILL.md +26 -1
  266. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  267. metadata +48 -2
  268. data/lib/rigor/inference/memo_audit.rb +0 -55
@@ -9,7 +9,8 @@ require_relative "../type"
9
9
  require_relative "guard_rebinding"
10
10
  require_relative "project_method_ownership"
11
11
  require_relative "refined_dispatch/own_method"
12
- require_relative "../run_scoped_slots"
12
+ require_relative "../typing_state"
13
+ require_relative "../memo_audit"
13
14
 
14
15
  module Rigor
15
16
  module Inference
@@ -116,19 +117,38 @@ module Rigor
116
117
  nil
117
118
  end
118
119
 
119
- DEFINITION_COUNTS_KEY = RunScopedSlots.register(:__rigor_self_call_ivar_writes_definition_counts__)
120
- private_constant :DEFINITION_COUNTS_KEY
120
+ # ADR-129 WD1 — a run-scoped Memo: the `[deferred_ranges, counts]` pair is one table and the counts computed
121
+ # from it alone, so the counts are a function of the table identity the pair is keyed on.
122
+ DEFINITION_COUNTS = TypingState.memo(
123
+ :self_call_ivar_writes_definition_counts,
124
+ run_scoped: true, audit: "self-call ivar definition counts",
125
+ reason: "the instance-def counts of one deferred-ranges table; keyed on its identity"
126
+ )
127
+ private_constant :DEFINITION_COUNTS
121
128
 
122
129
  # `{[owner, name] => the number of instance `def` rows of name the project records for owner}`, over every
123
130
  # file of `deferred_ranges` (a `discovered_deferred_ranges` table). Built once per table and kept in ONE
124
131
  # thread slot keyed by the table's identity (#2300): the table is never written in place, a file whose rows
125
132
  # equal the seed's keeps the seed's table, and a scan of every file's rows per query was the cost once the
126
133
  # table held the whole project. The slot holds one table and its counts, the next table replaces them, and the
127
- # run that filled it releases it when it ends ({RunScopedSlots}, #2399).
134
+ # run that filled it releases it when it ends (run-scoped, #2399).
128
135
  def definition_counts(deferred_ranges)
129
- slot = Thread.current[DEFINITION_COUNTS_KEY]
130
- return slot[1] if slot && slot[0].equal?(deferred_ranges)
136
+ return count_definitions(deferred_ranges) if MemoAudit.enabled? && MemoAudit.suspended?
131
137
 
138
+ state = TypingState.current
139
+ slot = state[DEFINITION_COUNTS]
140
+ if slot && slot[0].equal?(deferred_ranges)
141
+ return MemoAudit.check("self-call ivar definition counts", slot[1], deferred_ranges.size) do
142
+ count_definitions(deferred_ranges)
143
+ end
144
+ end
145
+
146
+ counts = MemoAudit.key_audit("self-call ivar definition counts") { count_definitions(deferred_ranges) }
147
+ state[DEFINITION_COUNTS] = [deferred_ranges, counts]
148
+ counts
149
+ end
150
+
151
+ def count_definitions(deferred_ranges)
132
152
  counts = {}
133
153
  deferred_ranges.each_value do |rows|
134
154
  rows.each do |row|
@@ -138,7 +158,6 @@ module Rigor
138
158
  counts[key] = counts.fetch(key, 0) + 1
139
159
  end
140
160
  end
141
- Thread.current[DEFINITION_COUNTS_KEY] = [deferred_ranges, counts]
142
161
  counts
143
162
  end
144
163
 
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "closure_escape_analyzer"
6
+ require_relative "project_method_ownership"
7
+ require_relative "refined_dispatch"
8
+
9
+ module Rigor
10
+ module Inference
11
+ # Issue #2861 — whether a block-carrying call provably runs its literal block under the CALLER's `self`, so the
12
+ # block body is no more opaque than the scope around it (`Scope#entering_self_preserving_block`).
13
+ #
14
+ # Issue #316 enters every block with `self` unmodelled, because a DSL call (`RSpec.describe`, `Class.new`, a
15
+ # project `def run(&b) = instance_exec(&b)`) may run the block on another object, and the call cannot be told
16
+ # apart from `Array#each` without knowing the callee. Here the callee is known: the explicit receiver's type and
17
+ # method are one {ClosureEscapeAnalyzer} classifies as `:non_escaping` — a catalogued core iterator, `tap` /
18
+ # `then`, or a project class's inherited `Enumerable` method — each of which `yield`s, which runs the block
19
+ # with its own `self`. The project must not define the method anywhere the receiver looks it up
20
+ # ({ProjectMethodOwnership.defines?}): a project reopening of `Array#each` could `instance_exec` the block.
21
+ # A `def` inside `refine X do … end` is no such answer, so where the call site's in-effect refinements may
22
+ # define the name the call declines too (issue #2909) — a refined `Integer#times` or `Object#tap` could
23
+ # `instance_exec` the block just the same.
24
+ #
25
+ # The project must not define it on `Object`, `Kernel` or another owner every receiver reaches either
26
+ # ({.universally_defined?}). A receiverless call is the DSL shape and never answers true, nor does a receiver Rigor
27
+ # cannot see. A `self.` receiver is read like any other explicit one, through its type.
28
+ module SelfPreservingBlock
29
+ module_function
30
+
31
+ # `classification` is the receiver's {ClosureEscapeAnalyzer.classify} answer when the caller already holds it.
32
+ def call?(call_node, receiver_type, scope, classification: nil)
33
+ return false unless call_node.is_a?(Prism::CallNode) && call_node.block.is_a?(Prism::BlockNode)
34
+
35
+ return false if call_node.receiver.nil? || receiver_type.nil?
36
+
37
+ method_name = call_node.name
38
+ classification ||= ClosureEscapeAnalyzer.classify(receiver_type: receiver_type, method_name: method_name,
39
+ scope: scope)
40
+ return false unless classification == :non_escaping
41
+
42
+ project_shadow_free?(call_node, receiver_type, method_name, scope)
43
+ rescue StandardError
44
+ false
45
+ end
46
+
47
+ # The project-side half of {.call?}: nothing the project defines — a plain `def` on a lookup target or a
48
+ # {UNIVERSAL_OWNERS} entry, nor a refinement in effect at the call site — may answer the yielding method
49
+ # instead of the catalogued non-escaping one.
50
+ def project_shadow_free?(call_node, receiver_type, method_name, scope)
51
+ targets = ProjectMethodOwnership.targets(receiver_type)
52
+ return false if targets.nil? || targets.empty?
53
+ return false if universally_defined?(method_name, scope)
54
+
55
+ targets.none? { |class_name, kind| ProjectMethodOwnership.defines?(class_name, method_name, kind, scope) } &&
56
+ !refined_in_effect?(call_node, method_name, scope)
57
+ end
58
+
59
+ # The classes every receiver's lookup reaches: an instance of any class is an `Object` (a class object is also
60
+ # a `Module` and a `Class`), so a project method of the name on one of them, or on `Kernel`, which
61
+ # {ProjectMethodOwnership.defines?} reaches through `Object`'s ancestry, may be the one the call runs, whatever
62
+ # class the receiver names. `ClosureEscapeAnalyzer` answers `tap` / `then` / `yield_self` for every class
63
+ # without asking who defines them.
64
+ UNIVERSAL_OWNERS = %w[Object Module Class].freeze
65
+ private_constant :UNIVERSAL_OWNERS
66
+
67
+ # Whether the project defines `method_name` on a {UNIVERSAL_OWNERS} entry. A top-level `def` of the name is
68
+ # private, so an explicit-receiver call raises NoMethodError before any block runs, and is not counted.
69
+ def universally_defined?(method_name, scope)
70
+ UNIVERSAL_OWNERS.any? { |owner| ProjectMethodOwnership.defines?(owner, method_name, :instance, scope) }
71
+ end
72
+
73
+ # Issue #2909 — whether a refinement in effect at `call_node` may define `method_name`. The refined-class
74
+ # walk is not consulted: any in-effect row for the name declines, the direction ADR-5 prefers, and it covers
75
+ # `refine Object` / `refine Kernel` without a universal-owner path of its own. An empty list — no `using`
76
+ # reaches the node — is the common case and costs the one lookup. The answer reads the refinement table by
77
+ # method name, so `refinement:<name>` is recorded as {ExpressionTyper}'s refined dispatch records it.
78
+ def refined_in_effect?(call_node, method_name, scope)
79
+ list = scope.in_effect_refinements(call_node)
80
+ return false if list.empty?
81
+
82
+ InEffectRefinements.record_refinement_names(method_name)
83
+ RefinedDispatch.refined_anywhere?(scope, method_name, list)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -37,7 +37,8 @@ module Rigor
37
37
  # Known remainders, pinned in `spec/integration/definer_resolution_witness_spec.rb`: `class << self; prepend
38
38
  # P` is recorded as an `extend` (the chain places `P` after the singleton, so an own `def self.x` is trusted
39
39
  # against it); a concern is capable for every name; an own `def self.x` is trusted against its own level's
40
- # U2 hooks and same-class redefinitions; a `class Class; def inherited` monkeypatch is unseen; and a
40
+ # U2 hooks and same-class redefinitions, except a concern block's `def self.x` of the same name (#3005, which
41
+ # the read tests); a `class Class; def inherited` monkeypatch is unseen; and a
41
42
  # superclass that only `extend`s adds two forks, so every read on its subclasses declines.
42
43
  module SingletonHookDecline
43
44
  WILDCARD = "*"
@@ -65,7 +66,8 @@ module Rigor
65
66
  entry = chain.entries[index]
66
67
  # A singleton project entry on a singleton chain is always a level's class entry.
67
68
  own = !entry.external? && entry.side == :singleton && def_head(scope, entry.name, method_name) == entry.name
68
- own ? index : index + 1
69
+ # Issue #3005 — an own `def self.x` that a concern block's `def self.x` on its level may replace is tested.
70
+ own && !chain.hook_block_rival?(scope, index, method_name) ? index : index + 1
69
71
  end
70
72
 
71
73
  # What a class entry's answer is when its `def` was written in another module's body: `:copy` when that
@@ -92,14 +94,22 @@ module Rigor
92
94
  # Whether the class's singleton `def` is the very node `head`'s instance table holds, the one the fold
93
95
  # copied: a receiverless `def`. A `def C.x` or `class << C; def x; end` written inside `head`'s body has the
94
96
  # same nesting head and is the class's own, not a copy. (The walk records a `def C.x` written in `module X`
95
- # as `X#x` too, so identity alone does not tell it from a copy; its receiver does.)
97
+ # as `X#x` too, so identity alone does not tell it from a copy; its receiver does.) Issue #3155 — the walk no
98
+ # longer files that `def C.x` anywhere, so the copy is all the table holds; the definer's mark on the class
99
+ # (`ENVELOPE_OBJECT_SINGLETON_KIND`, {ScopeIndexer#record_object_singleton}) still says the class has its own.
96
100
  def copied_from?(scope, head, method_name, owner)
97
101
  node = scope.singleton_def_for(owner, method_name)
98
102
  return false if node.nil? || !node.receiver.nil?
103
+ return false if foreign_singleton_definer?(scope, owner, method_name)
99
104
 
100
105
  scope.user_def_for(head, method_name).equal?(node)
101
106
  end
102
107
 
108
+ def foreign_singleton_definer?(scope, owner, method_name)
109
+ key = [Scope::DiscoveryIndex::ENVELOPE_OBJECT_SINGLETON_KIND, method_name.to_sym]
110
+ scope.parameter_envelopes_of(owner).key?(key)
111
+ end
112
+
103
113
  # The innermost `Module.nesting` entry `owner`'s singleton `def` of the name was written in, or nil.
104
114
  def def_head(scope, owner, method_name)
105
115
  node = scope.singleton_def_for(owner, method_name)
@@ -170,15 +180,40 @@ module Rigor
170
180
 
171
181
  # The memoised `[verdict, edges]` of one test, its edges replayed while a recording is active.
172
182
  def verdict(kind, key)
173
- verdict, edges = @memo[[kind, key]] ||= begin
183
+ memo_key = [kind, key]
184
+ entry = @memo[memo_key]
185
+ if entry.nil?
174
186
  edges = []
175
- value = yield(edges)
176
- [value ? true : false, edges.concat(own_edges(kind, key)).uniq.freeze].freeze
187
+ value = MemoAudit.key_audit("singleton hook verdict") { yield(edges) }
188
+ entry = @memo[memo_key] = [value ? true : false, edges.concat(own_edges(kind, key)).uniq.freeze].freeze
189
+ else
190
+ entry = MemoAudit.check("singleton hook verdict", entry, kind, key) { fresh_verdict(kind, key) }
177
191
  end
192
+ verdict, edges = entry
178
193
  replay(edges) if @recording
179
194
  verdict
180
195
  end
181
196
 
197
+ # ADR-129 G2 — a memoised verdict rebuilt for the audit by a new Context, whose memo is a fresh bucket's
198
+ # while the recomputation runs ({Scope::ResolutionChain.hook_memo}), its dependency reads withheld.
199
+ def fresh_verdict(kind, key)
200
+ Analysis::DependencyRecorder.withhold { Context.new(@scope, @flavor).verdict_entry(kind, key) }.first
201
+ end
202
+
203
+ protected
204
+
205
+ # The `[verdict, edges]` entry of one test, computed through {#verdict} on this Context's memo.
206
+ def verdict_entry(kind, key)
207
+ case kind
208
+ when :project then verdict(:project, key) { project_capable?(key) }
209
+ when :external then verdict(:external, key) { external_capable?(key) }
210
+ else instance_level_capable?(key)
211
+ end
212
+ @memo[[kind, key]]
213
+ end
214
+
215
+ private
216
+
182
217
  def own_edges(kind, key)
183
218
  return entry_edges(key) if kind == :external
184
219
  return EMPTY_EDGES if kind == :instance_level
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../source/constant_path"
4
+ require_relative "../source/parameter_envelope"
4
5
 
5
6
  module Rigor
6
7
  module Inference
@@ -45,18 +46,47 @@ module Rigor
45
46
  name = receiver_constant_name(call_node, receiver_type)
46
47
  return false if name.nil?
47
48
 
48
- scope.discovered_method?(name, method_name, :singleton)
49
+ scope.discovered_method?(name, method_name, :singleton) || foreign_singleton?(scope, name, method_name)
50
+ end
51
+
52
+ # Issue #3016 — `def Const.x` written outside `Const`'s own body, and `Const.define_singleton_method(:x)`, record
53
+ # no `:singleton` row: {ScopeIndexer} files them as an opaque `[:singleton, x]` envelope on every name the
54
+ # constant can denote from the def site. That envelope is the evidence the method exists on the constant's
55
+ # object. The read records the constant's class edge, and that edge's file set holds every file writing such a
56
+ # def or call, so adding or removing one re-checks the call.
57
+ #
58
+ # A computed `Const.define_singleton_method(name)` may define any name, so the singleton side's open-names mark it
59
+ # files declines every call through the constant.
60
+ def foreign_singleton?(scope, name, method_name)
61
+ bucket = scope.parameter_envelopes_of(name)
62
+ bucket.key?([:singleton, method_name.to_sym]) ||
63
+ bucket.key?(Scope::DiscoveryIndex::ENVELOPE_OPEN_SINGLETON_NAMES_MARK)
49
64
  end
50
65
 
51
66
  # The `Prism::DefNode` for `method_name` in the constant's singleton body, or nil. The inference tier's
52
67
  # entry point.
68
+ #
69
+ # Issue #3018 — nil too when another definer of the name on the constant may replace that def: a `def Const.x`,
70
+ # a `Const.define_singleton_method(:x)` or a `singleton_class.class_eval` def joins an opaque envelope into the
71
+ # name's `[:singleton, x]` slot, and a computed `define_singleton_method` files the open-names mark. The body
72
+ # def's own contribution is its parameter envelope, never opaque, so a lone body def keeps its typed answer.
73
+ # Declined whatever the source order, since a two-file project has none.
53
74
  def def_node_for(call_node, receiver_type, method_name, scope)
54
75
  return nil if scope.nil?
55
76
 
56
77
  name = receiver_constant_name(call_node, receiver_type)
57
78
  return nil if name.nil?
58
79
 
59
- scope.singleton_def_for(name, method_name)
80
+ def_node = scope.singleton_def_for(name, method_name)
81
+ return nil if def_node.nil? || replaceable?(scope, name, method_name)
82
+
83
+ def_node
84
+ end
85
+
86
+ def replaceable?(scope, name, method_name)
87
+ bucket = scope.parameter_envelopes_of(name)
88
+ Source::ParameterEnvelope.opaque?(bucket[[:singleton, method_name.to_sym]]) ||
89
+ bucket.key?(Scope::DiscoveryIndex::ENVELOPE_OPEN_SINGLETON_NAMES_MARK)
60
90
  end
61
91
  end
62
92
  end