rigortype 0.3.5 → 0.3.7

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 (266) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/data/builtins/ruby_core/date.yml +6 -3
  4. data/data/builtins/ruby_core/enumerable.yml +1 -0
  5. data/data/builtins/ruby_core/exception.yml +2 -1
  6. data/data/builtins/ruby_core/file.yml +28 -14
  7. data/data/builtins/ruby_core/hash.yml +5 -2
  8. data/data/builtins/ruby_core/io.yml +33 -16
  9. data/data/builtins/ruby_core/random.yml +2 -1
  10. data/data/builtins/ruby_core/re.yml +6 -3
  11. data/data/builtins/ruby_core/struct.yml +2 -0
  12. data/data/builtins/ruby_core/time.yml +2 -1
  13. data/data/gem_overlay/activesupport/core_ext.rbs +442 -11
  14. data/docs/handbook/02-everyday-types.md +1 -1
  15. data/docs/handbook/04-tuples-and-shapes.md +1 -1
  16. data/docs/handbook/08-understanding-errors.md +1 -1
  17. data/docs/handbook/09-plugins.md +2 -2
  18. data/docs/handbook/10-sorbet.md +1 -1
  19. data/docs/handbook/README.md +2 -2
  20. data/docs/handbook/appendix-go.md +1 -1
  21. data/docs/handbook/appendix-java-csharp.md +2 -2
  22. data/docs/handbook/appendix-mypy.md +1 -1
  23. data/docs/handbook/appendix-protocols-and-structural-typing.md +2 -2
  24. data/docs/handbook/appendix-rust.md +1 -1
  25. data/docs/handbook/appendix-type-theory.md +2 -2
  26. data/docs/handbook/appendix-typescript.md +4 -4
  27. data/docs/manual/02-cli-reference.md +29 -16
  28. data/docs/manual/03-configuration.md +1 -1
  29. data/docs/manual/04-diagnostics.md +12 -0
  30. data/docs/manual/05-inspecting-types.md +20 -8
  31. data/docs/manual/07-plugins.md +4 -0
  32. data/docs/manual/08-skills.md +1 -1
  33. data/docs/manual/11-ci.md +1 -1
  34. data/docs/manual/12-caching.md +1 -1
  35. data/docs/manual/15-type-protection-coverage.md +1 -1
  36. data/docs/manual/18-removing-dead-code.md +13 -4
  37. data/docs/manual/19-effect-labels.md +19 -12
  38. data/docs/manual/README.md +2 -2
  39. data/docs/manual/plugins/rigor-actionmailer.md +4 -4
  40. data/docs/manual/plugins/rigor-actionpack.md +61 -0
  41. data/docs/manual/plugins/rigor-activejob.md +3 -3
  42. data/docs/manual/plugins/rigor-activerecord.md +78 -5
  43. data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
  44. data/docs/manual/plugins/rigor-rails-i18n.md +5 -5
  45. data/docs/manual/plugins/rigor-rspec.md +6 -2
  46. data/docs/manual/plugins/rigor-sidekiq.md +22 -0
  47. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
  48. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
  49. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
  50. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
  51. data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
  52. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
  53. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
  54. data/lib/rigor/analysis/check_rules.rb +383 -48
  55. data/lib/rigor/analysis/crash_signature.rb +116 -0
  56. data/lib/rigor/analysis/dependency_recorder.rb +18 -3
  57. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  58. data/lib/rigor/analysis/diagnostic.rb +17 -6
  59. data/lib/rigor/analysis/effects_cache_probe.rb +132 -0
  60. data/lib/rigor/analysis/incremental.rb +28 -0
  61. data/lib/rigor/analysis/incremental_session.rb +44 -9
  62. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
  63. data/lib/rigor/analysis/reachability/graph.rb +44 -16
  64. data/lib/rigor/analysis/reachability/plugin_roots.rb +11 -8
  65. data/lib/rigor/analysis/reachability/scan.rb +27 -7
  66. data/lib/rigor/analysis/reachability/scan_cache.rb +130 -0
  67. data/lib/rigor/analysis/result.rb +32 -0
  68. data/lib/rigor/analysis/run_cache_key.rb +53 -1
  69. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
  70. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +14 -8
  71. data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
  72. data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
  73. data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
  74. data/lib/rigor/analysis/runner.rb +298 -38
  75. data/lib/rigor/analysis/worker_session.rb +10 -1
  76. data/lib/rigor/bleeding_edge.rb +7 -6
  77. data/lib/rigor/builtins/hkt_builtins.rb +45 -6
  78. data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
  79. data/lib/rigor/builtins/static_return_refinements.rb +20 -1
  80. data/lib/rigor/cache/descriptor.rb +42 -2
  81. data/lib/rigor/cache/file_digest.rb +20 -2
  82. data/lib/rigor/cache/incremental_snapshot.rb +40 -3
  83. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
  84. data/lib/rigor/cache/store.rb +10 -1
  85. data/lib/rigor/cli/check_command.rb +67 -48
  86. data/lib/rigor/cli/coverage_command.rb +28 -24
  87. data/lib/rigor/cli/coverage_mutation.rb +13 -17
  88. data/lib/rigor/cli/coverage_scan.rb +47 -6
  89. data/lib/rigor/cli/doc_links.rb +100 -0
  90. data/lib/rigor/cli/docs_command.rb +32 -2
  91. data/lib/rigor/cli/effects_command.rb +27 -2
  92. data/lib/rigor/cli/effects_diff_renderer.rb +82 -12
  93. data/lib/rigor/cli/effects_explain_renderer.rb +6 -3
  94. data/lib/rigor/cli/effects_snapshot_command.rb +52 -7
  95. data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
  96. data/lib/rigor/cli/fused_protection_report.rb +23 -2
  97. data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
  98. data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
  99. data/lib/rigor/cli/mutation_protection_report.rb +31 -2
  100. data/lib/rigor/cli/sig_gen_command.rb +18 -0
  101. data/lib/rigor/cli/type_of_command.rb +188 -41
  102. data/lib/rigor/cli/type_of_renderer.rb +72 -3
  103. data/lib/rigor/cli/type_scan_command.rb +10 -1
  104. data/lib/rigor/cli/unused_command.rb +65 -16
  105. data/lib/rigor/cli.rb +22 -9
  106. data/lib/rigor/configuration.rb +7 -4
  107. data/lib/rigor/effects/definition_lines.rb +100 -0
  108. data/lib/rigor/effects/envelope_check.rb +18 -1
  109. data/lib/rigor/effects/liskov_check.rb +17 -8
  110. data/lib/rigor/effects/plugin_facts.rb +1 -1
  111. data/lib/rigor/effects/scanner.rb +2 -3
  112. data/lib/rigor/effects/signature_sources.rb +13 -2
  113. data/lib/rigor/effects/snapshot.rb +53 -21
  114. data/lib/rigor/effects/snapshot_diff.rb +26 -3
  115. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
  116. data/lib/rigor/environment/lockfile_resolver.rb +15 -5
  117. data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
  118. data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
  119. data/lib/rigor/environment/rbs_loader.rb +525 -46
  120. data/lib/rigor/environment.rb +38 -15
  121. data/lib/rigor/inference/acceptance.rb +172 -20
  122. data/lib/rigor/inference/captured_locals.rb +62 -0
  123. data/lib/rigor/inference/content_join.rb +347 -0
  124. data/lib/rigor/inference/def_handle.rb +15 -3
  125. data/lib/rigor/inference/def_node_resolver.rb +62 -3
  126. data/lib/rigor/inference/expression_typer.rb +1020 -141
  127. data/lib/rigor/inference/fork_map.rb +6 -1
  128. data/lib/rigor/inference/hkt_reducer.rb +12 -1
  129. data/lib/rigor/inference/hkt_registry.rb +46 -0
  130. data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
  131. data/lib/rigor/inference/index_write_widening.rb +48 -0
  132. data/lib/rigor/inference/indexed_narrowing.rb +14 -0
  133. data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
  134. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
  135. data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
  136. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
  137. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
  138. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
  139. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
  140. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
  141. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
  142. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
  143. data/lib/rigor/inference/method_dispatcher.rb +155 -14
  144. data/lib/rigor/inference/method_parameter_binder.rb +3 -3
  145. data/lib/rigor/inference/mutation_widening.rb +244 -174
  146. data/lib/rigor/inference/narrowing.rb +331 -27
  147. data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
  148. data/lib/rigor/inference/pre_eval_constants.rb +15 -0
  149. data/lib/rigor/inference/precision_scanner.rb +5 -2
  150. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  151. data/lib/rigor/inference/rbs_type_translator.rb +89 -39
  152. data/lib/rigor/inference/scope_indexer.rb +1493 -140
  153. data/lib/rigor/inference/statement_evaluator.rb +276 -91
  154. data/lib/rigor/inference/struct_fold_safety.rb +186 -27
  155. data/lib/rigor/inference/synthetic_method_scanner.rb +7 -0
  156. data/lib/rigor/inference/version_guard.rb +229 -0
  157. data/lib/rigor/language_server/selection_range_provider.rb +1 -1
  158. data/lib/rigor/plugin/base.rb +3 -2
  159. data/lib/rigor/plugin/inflector.rb +14 -5
  160. data/lib/rigor/plugin/io_boundary.rb +107 -4
  161. data/lib/rigor/plugin/loader.rb +14 -0
  162. data/lib/rigor/protection/analysis_guard.rb +60 -0
  163. data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
  164. data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
  165. data/lib/rigor/protection/discovery_seed.rb +1 -1
  166. data/lib/rigor/protection/measurement_integrity.rb +36 -0
  167. data/lib/rigor/protection/mutation_scanner.rb +61 -6
  168. data/lib/rigor/protection/mutator.rb +121 -18
  169. data/lib/rigor/rbs_extended/envelope_scanner.rb +9 -0
  170. data/lib/rigor/reflection.rb +182 -30
  171. data/lib/rigor/scope/discovery_index.rb +39 -1
  172. data/lib/rigor/scope.rb +385 -4
  173. data/lib/rigor/sig_gen/classification.rb +12 -1
  174. data/lib/rigor/sig_gen/generator.rb +254 -5
  175. data/lib/rigor/sig_gen/renderer.rb +1 -6
  176. data/lib/rigor/sig_gen/writer.rb +3 -0
  177. data/lib/rigor/source/constant_path.rb +79 -2
  178. data/lib/rigor/triage/catalogue.rb +1 -1
  179. data/lib/rigor/type/combinator.rb +10 -0
  180. data/lib/rigor/type/maybe.rb +47 -0
  181. data/lib/rigor/type/refined.rb +1 -2
  182. data/lib/rigor/type/result.rb +53 -0
  183. data/lib/rigor/type.rb +2 -0
  184. data/lib/rigor/version.rb +1 -1
  185. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
  186. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
  187. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
  188. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
  189. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
  190. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
  191. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
  192. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
  193. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
  194. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
  195. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
  196. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
  197. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
  198. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
  199. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
  200. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
  201. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
  202. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
  203. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
  204. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
  205. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
  206. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
  207. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
  208. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
  209. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
  210. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
  211. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
  212. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
  213. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
  214. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
  215. data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
  216. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
  217. data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
  218. data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
  219. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
  220. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
  221. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
  222. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
  223. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
  224. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
  225. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
  226. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
  227. data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
  228. data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
  229. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
  230. data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
  231. data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
  232. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
  233. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
  234. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  235. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
  236. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
  237. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
  238. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
  239. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
  240. data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
  241. data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
  242. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
  243. data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
  244. data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
  245. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
  246. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
  247. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
  248. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
  249. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
  250. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
  251. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
  252. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
  253. data/sig/rigor/analysis/baseline.rbs +1 -1
  254. data/sig/rigor/environment.rbs +3 -0
  255. data/sig/rigor/inference.rbs +4 -1
  256. data/sig/rigor/plugin/io_boundary.rbs +2 -0
  257. data/sig/rigor/reflection.rbs +3 -1
  258. data/sig/rigor/scope.rbs +25 -1
  259. data/sig/rigor/type.rbs +95 -1
  260. data/sig/rigor.rbs +16 -0
  261. data/skills/rigor-ci-setup/SKILL.md +2 -2
  262. data/skills/rigor-editor-setup/SKILL.md +2 -2
  263. data/skills/rigor-mcp-setup/SKILL.md +2 -2
  264. data/skills/rigor-monkeypatch-resolve/SKILL.md +1 -1
  265. data/skills/rigor-plugin-review/SKILL.md +3 -3
  266. metadata +73 -3
@@ -29,7 +29,10 @@ require_relative "method_dispatcher/regexp_folding"
29
29
  require_relative "method_dispatcher/cgi_folding"
30
30
  require_relative "method_dispatcher/uri_folding"
31
31
  require_relative "method_dispatcher/set_folding"
32
+ require_relative "method_dispatcher/json_folding"
32
33
  require_relative "method_dispatcher/kernel_dispatch"
34
+ require_relative "method_dispatcher/universal_object_dispatch"
35
+ require_relative "method_dispatcher/singleton_mixin_dispatch"
33
36
  require_relative "method_dispatcher/method_folding"
34
37
 
35
38
  module Rigor
@@ -119,6 +122,11 @@ module Rigor
119
122
  # skip this tier.
120
123
  plugin_result = try_plugin_contribution(call_node, scope, receiver_type)
121
124
  if plugin_result
125
+ # Issue #653 — this tier answering is what makes the site's type the plugin's rather than the
126
+ # RBS's, and `call.undefined-method` reads that record instead of re-deciding from the receiver's
127
+ # (possibly partial) signature. Recorded for EVERY plugin answer, dynamic or precise: the
128
+ # incoherence the record closes is about which subsystem typed the call, not about the type.
129
+ scope&.record_plugin_typed_call(call_node)
122
130
  if plugin_result.is_a?(Type::Dynamic)
123
131
  scope&.record_dynamic_origin(call_node, DynamicOrigin::FRAMEWORK_DSL_BOUNDARY)
124
132
  end
@@ -144,6 +152,12 @@ module Rigor
144
152
  static_refinement = try_static_refinement(receiver_type, method_name, arg_types)
145
153
  return static_refinement if static_refinement
146
154
 
155
+ # `Foo.instance` for a class that includes the stdlib `Singleton` mixin. Ruby grows that method through
156
+ # an `included` hook and the upstream RBS leaves `Singleton::SingletonClassMethods` empty, so nothing
157
+ # below can answer it. See {SingletonMixinDispatch} for why the value matters more than the site count.
158
+ singleton_mixin = SingletonMixinDispatch.try_dispatch(context)
159
+ return singleton_mixin if singleton_mixin
160
+
147
161
  rbs_result = RbsDispatch.try_dispatch(context)
148
162
  if rbs_result
149
163
  record_boundary_cross_if_applicable(receiver_type, method_name, rbs_result, environment)
@@ -218,6 +232,15 @@ module Rigor
218
232
  stub_result = try_synthesized_stub_type(receiver_type, environment)
219
233
  return stub_result if stub_result
220
234
 
235
+ # The receiver-independent `BasicObject` / `Object` / `Kernel` selectors, for a receiver no tier
236
+ # above could name. `x.nil?` is `bool` whatever `x` is; without this tier it was `Dynamic[Top]`
237
+ # like every other call on an untyped receiver. Sits here, below every real resolution tier, so a
238
+ # nameable receiver always answers from its own signature first — this only ever replaces a
239
+ # `Dynamic[Top]` the typer was about to produce. See {UniversalObjectDispatch} for the table and
240
+ # for the selectors deliberately left out of it.
241
+ universal_result = UniversalObjectDispatch.try_dispatch(context)
242
+ return universal_result if universal_result
243
+
221
244
  # Slice 7 phase 10 — user-class ancestor fallback. When the receiver is `Nominal[T]` or
222
245
  # `Singleton[T]` for a class not in the RBS environment (typically a user-defined class),
223
246
  # retry the dispatch against the implicit ancestor: `Nominal[Object]` for instance
@@ -305,9 +328,13 @@ module Rigor
305
328
  # on. `Nominal[X]` looks up instance methods on X; `Singleton[X]` looks up singleton
306
329
  # methods on X. Other carriers return `[nil, nil]` so the tier declines.
307
330
  def discovered_method_lookup(receiver_type)
331
+ # #525 — the ADR-48 member carriers name their class too: a `Line = Struct.new(...) do ... end`
332
+ # value is a StructInstance whose block-def methods are discovered under "Line".
308
333
  case receiver_type
309
- when Type::Nominal then [receiver_type.class_name, :instance]
310
- when Type::Singleton then [receiver_type.class_name, :singleton]
334
+ when Type::Nominal, Type::StructInstance, Type::DataInstance
335
+ [receiver_type.class_name, :instance]
336
+ when Type::Singleton, Type::StructClass, Type::DataClass
337
+ [receiver_type.class_name, :singleton]
311
338
  else [nil, nil]
312
339
  end
313
340
  end
@@ -711,7 +738,7 @@ module Rigor
711
738
  #
712
739
  # The precise-tier folders, consulted in order via the uniform `_DispatchTier` interface
713
740
  # (`try_dispatch(CallContext) -> Type?`). Order is significant: ConstantFolding's
714
- # exact-value folds win first, the eight stdlib singleton folders sit in the middle (each
741
+ # exact-value folds win first, the stdlib singleton folders sit in the middle (each
715
742
  # gates on a distinct `Singleton` receiver, so their relative order is immaterial), and
716
743
  # BlockFolding runs last because its rules only apply to block-taking calls — the cheaper
717
744
  # arity folds above it filter the common cases first. Adding a precise tier is a one-line
@@ -721,13 +748,13 @@ module Rigor
721
748
  ].freeze)
722
749
  private_constant :PRECISE_TIERS_HEAD
723
750
 
724
- # ADR-53 re-review follow-up (gate-by-held-key applied to the built-in tiers): the eight
725
- # stdlib singleton folders are mutually exclusive — each fires only on `Singleton[<its
751
+ # ADR-53 re-review follow-up (gate-by-held-key applied to the built-in tiers): the stdlib
752
+ # singleton folders are mutually exclusive — each fires only on `Singleton[<its
726
753
  # class>]`, the first check in every `try_dispatch` — so at most one can match a given
727
754
  # receiver and their relative trial order was never observable. Compiling them into a
728
- # class-name table turns eight no-op trials per call into one Hash read, skipped entirely
755
+ # class-name table turns nine no-op trials per call into one Hash read, skipped entirely
729
756
  # when the receiver is not a `Singleton` (the overwhelmingly common case). The table sits
730
- # where the eight sat in the old flat list: after ShapeDispatch, before KernelDispatch.
757
+ # where the original eight sat in the old flat list: after ShapeDispatch, before KernelDispatch.
731
758
  STDLIB_SINGLETON_FOLDERS = Ractor.make_shareable({
732
759
  "File" => FileFolding,
733
760
  "Shellwords" => ShellwordsFolding,
@@ -736,7 +763,8 @@ module Rigor
736
763
  "Regexp" => RegexpFolding,
737
764
  "CGI" => CGIFolding,
738
765
  "URI" => URIFolding,
739
- "Set" => SetFolding
766
+ "Set" => SetFolding,
767
+ "JSON" => JSONFolding
740
768
  }.freeze)
741
769
  private_constant :STDLIB_SINGLETON_FOLDERS
742
770
 
@@ -975,7 +1003,9 @@ module Rigor
975
1003
  return nil unless arg_types.size == 1
976
1004
 
977
1005
  parent = arg_types.first
978
- return parent if parent.is_a?(Type::Singleton)
1006
+ factory_parent = parent.is_a?(Type::StructClass) || parent.is_a?(Type::DataClass)
1007
+ return nil if factory_parent && context&.call_node&.block
1008
+ return parent if parent.is_a?(Type::Singleton) || factory_parent
979
1009
 
980
1010
  nil
981
1011
  end
@@ -1022,8 +1052,8 @@ module Rigor
1022
1052
 
1023
1053
  # `Array.new(n, value)` and `Array.new(n)` (no value, default `nil`) lift to a per-position
1024
1054
  # `Tuple[…]` when `n` is a small `Constant<Integer>`. Cap at `ARRAY_NEW_TUPLE_LIMIT` (16)
1025
- # so a `Array.new(1_000_000)` does not balloon the carrier; oversize calls fall back to
1026
- # `Nominal[Array]`.
1055
+ # so a `Array.new(1_000_000)` does not balloon the carrier; oversize calls fall through to
1056
+ # the element-parameter seed below.
1027
1057
  #
1028
1058
  # #317 — `Array.new(n) { |i| ... }` fills every slot from the BLOCK's return type instead
1029
1059
  # of the two-arg `(n, default_value)` overload's `nil` fill. The block and the trailing
@@ -1039,10 +1069,121 @@ module Rigor
1039
1069
  return nil if arg_types.empty? || arg_types.size > 2
1040
1070
 
1041
1071
  size = array_new_size(arg_types.first)
1042
- return nil if size.nil? || size.negative? || size > ARRAY_NEW_TUPLE_LIMIT
1072
+ if size && !size.negative? && size <= ARRAY_NEW_TUPLE_LIMIT
1073
+ return Type::Combinator.tuple_of(*Array.new(size, block_type || array_new_fill(arg_types[1])))
1074
+ end
1075
+
1076
+ array_new_seed(arg_types, block_type)
1077
+ end
1078
+
1079
+ # The carrier for a constructor whose size is not a small literal — `Array.new(n)`,
1080
+ # `Array.new(n, v)`, `Array.new(n) { … }`.
1081
+ #
1082
+ # This must never be a BARE `Array`. A nominal with no type argument carries no element
1083
+ # arm, and the ADR-56 block/loop seams read exactly that as an ELEMENTLESS seed — a fresh
1084
+ # accumulator whose every store the seam saw — so they CLOSE the carrier over the block's
1085
+ # own stores. `acc = Array.new(n); [1].each { acc.push(1) }` then read `Array[1]` for an
1086
+ # array whose other `n` slots hold `nil`, and `acc = Array.new(n, "x")` read `Array[1]` and
1087
+ # drew `undefined method 'upcase'` on the correct `acc.first.upcase` (issue #615). Handing
1088
+ # the seams a real element arm is what puts the constructor under the #586 rule, which
1089
+ # keeps every seed arm through the join.
1090
+ #
1091
+ # The element itself is the fill value's / block result's type, value-pin widened for the
1092
+ # reason `hash_new_lift` widens its default: the slots are rewritten over the array's
1093
+ # lifetime, so pinning the parameter to the literal would let a later `a[i] == "x"`
1094
+ # constant-fold.
1095
+ #
1096
+ # **The no-fill form seeds the GRADUAL element, not `nil`.** `Array.new(n)` really does put
1097
+ # a `nil` in every slot, but a `Nominal` seed gets DECLARED-carrier semantics downstream:
1098
+ # the #586 join keeps a seed arm forever, and `MutationWidening#widen_for_mutator` declines
1099
+ # a `Nominal` outright, so no whole-array rewrite — `[]=`, `fill`, `map!`, `replace`,
1100
+ # `concat` — can ever retract it. The placeholder would become a permanent nil possibility
1101
+ # over the allocate-then-fill idiom that is the whole point of the constructor:
1102
+ #
1103
+ # dp = Array.new(xs.size); dp[0] = 1
1104
+ # (1...n).each { |i| dp[i] = dp[i - 1] + xs[i] } # `undefined method '+' for nil`
1105
+ #
1106
+ # buf = Array.new(256); 256.times { |i| buf[i] = i.to_s }
1107
+ # buf.each { |c| c.upcase } # possible nil receiver
1108
+ #
1109
+ # Both are correct Ruby, and both went from quiet to an ERROR on the `nil` seed. The true
1110
+ # positive the `nil` would buy — `Array.new(n).first.upcase` on an array nothing ever wrote
1111
+ # — is not worth the dominant idiom, so the no-fill form stays gradual whatever the size
1112
+ # argument reads as. (That also happens to absorb Ruby's array-convertible COPY overload:
1113
+ # `Array.new([1, 2])` is `[1, 2]`, not two nils.)
1114
+ def array_new_seed(arg_types, block_type)
1115
+ fill = block_type || arg_types[1]
1116
+ return array_seed_of(Type::Combinator.untyped) if fill.nil?
1117
+
1118
+ element = array_new_element(fill)
1119
+ # `Array.new(n, nil)` is the same allocate-then-fill idiom as `Array.new(n)`, spelled with the
1120
+ # placeholder made explicit — concurrent-ruby writes `@Resolutions = ::Array.new(count, nil)` and
1121
+ # fills it by index. A nil-only element would be a PERMANENT nil arm (a Nominal carrier's element is
1122
+ # never widened by a later `[]=` / `fill` / `replace`), so every read of a filled slot would draw
1123
+ # `undefined method '…' for nil` on correct code. Same trade as the no-fill form above: the seam
1124
+ # needs an arm, not a precise one.
1125
+ element = Type::Combinator.untyped if nil_only_element?(element)
1126
+ array_seed_of(element)
1127
+ end
1043
1128
 
1044
- fill = block_type || array_new_fill(arg_types[1])
1045
- Type::Combinator.tuple_of(*Array.new(size, fill))
1129
+ # Whether the widened fill leaves nothing but `nil` behind — see {#array_new_seed}.
1130
+ def nil_only_element?(element)
1131
+ members = element.is_a?(Type::Union) ? element.members : [element]
1132
+ members.all? do |member|
1133
+ (member.is_a?(Type::Constant) && member.value.nil?) ||
1134
+ (member.is_a?(Type::Nominal) && member.class_name == "NilClass")
1135
+ end
1136
+ end
1137
+
1138
+ def array_seed_of(element)
1139
+ Type::Combinator.nominal_of("Array", type_args: [element])
1140
+ end
1141
+
1142
+ # A fill / block result as an element PARAMETER. Value pinning is dropped, and a literal
1143
+ # container widens to its nominal: `Array.new(n) { [] }` builds n INDEPENDENT arrays that
1144
+ # the program then appends to, and `Array[Tuple[]]` claims every one of them stays empty —
1145
+ # the adjacency-list idiom would read `adj[i].first` as `nil`. Both conversions go through
1146
+ # {MutationWidening}'s own helpers, the single owner of "literal container → parameterised
1147
+ # nominal" that `MemberShapeProjection` already borrows.
1148
+ #
1149
+ # The walk is **recursive**, through `Union` members and through a container's own elements /
1150
+ # values alike. Every one of those positions is a fresh object per constructed slot that the
1151
+ # program goes on to mutate, so stopping at the outermost level only moved the wrong-precise
1152
+ # answer one level in: `a = Array.new(n) { [[1]] }; a[0][0] << 5` left `Array[Array[[1]]]`
1153
+ # and folded `a[0][0].last == 5` always-falsey on correct code. Judging a `Union` wholesale
1154
+ # was the same defect one axis over — `Array.new(n) { flag ? [1] : [2] }` kept both arms as
1155
+ # fixed-arity tuples.
1156
+ #
1157
+ # `depth` is defensive only. A `Type` carrier is an immutable value object assembled
1158
+ # bottom-up (`Tuple.new(elements)`, `HashShape.new(pairs)`), so it cannot contain itself and
1159
+ # this walk is bounded by the source literal's own nesting; the cap costs nothing on any
1160
+ # literal a person writes and stops a malformed carrier from recursing forever.
1161
+ ARRAY_NEW_FILL_DEPTH_LIMIT = 8
1162
+ private_constant :ARRAY_NEW_FILL_DEPTH_LIMIT
1163
+
1164
+ def array_new_element(type, depth = 0)
1165
+ return Type::Combinator.untyped if depth > ARRAY_NEW_FILL_DEPTH_LIMIT
1166
+
1167
+ case type
1168
+ when Type::Tuple then MutationWidening.widen_tuple(recursed_tuple(type, depth))
1169
+ when Type::HashShape then MutationWidening.widen_hash_shape(recursed_hash_shape(type, depth))
1170
+ when Type::Union then Type::Combinator.union(*type.members.map { |m| array_new_element(m, depth + 1) })
1171
+ else Type::Combinator.widen_value_pinned(type)
1172
+ end
1173
+ end
1174
+
1175
+ # The same tuple / hash shape with every element / value already widened, so the
1176
+ # {MutationWidening} helper that consumes it sees nominals where the literal had pins. The
1177
+ # rebuilt carrier is a throwaway argument — never surfaced — and the helpers read only
1178
+ # `elements` / `pairs`, so no policy field rides on it.
1179
+ def recursed_tuple(type, depth)
1180
+ Type::Combinator.tuple_of(*type.elements.map { |e| array_new_element(e, depth + 1) })
1181
+ end
1182
+
1183
+ def recursed_hash_shape(type, depth)
1184
+ Type::Combinator.hash_shape_of(
1185
+ type.pairs.transform_values { |v| array_new_element(v, depth + 1) }
1186
+ )
1046
1187
  end
1047
1188
 
1048
1189
  def array_new_size(type)
@@ -332,8 +332,7 @@ module Rigor
332
332
  when :rest_positional
333
333
  Type::Combinator.nominal_of("Array", type_args: [translated])
334
334
  when :rest_keyword
335
- symbol_nominal = Type::Combinator.nominal_of("Symbol")
336
- Type::Combinator.nominal_of("Hash", type_args: [symbol_nominal, translated])
335
+ Type::Combinator.nominal_of("Hash", type_args: [Type::Combinator.nominal_of("Symbol"), translated])
337
336
  else
338
337
  translated
339
338
  end
@@ -344,7 +343,8 @@ module Rigor
344
343
  RbsTypeTranslator.translate(
345
344
  rbs_type,
346
345
  self_type: self_type,
347
- instance_type: instance_type
346
+ instance_type: instance_type,
347
+ alias_expander: @environment&.rbs_loader
348
348
  )
349
349
  rescue StandardError
350
350
  Type::Combinator.untyped