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
@@ -15,6 +15,7 @@ require_relative "vendored_signature_plan"
15
15
  require_relative "ruby_library_roots"
16
16
  require_relative "core_root"
17
17
  require_relative "../rooted_glob"
18
+ require_relative "../typing_state"
18
19
 
19
20
  module Rigor
20
21
  class Environment
@@ -35,6 +36,12 @@ module Rigor
35
36
  # See docs/internal-spec/inference-engine.md for the binding contract.
36
37
  # rubocop:disable-next Metrics/ClassLength
37
38
  class RbsLoader
39
+ # ADR-129 G4 — keyed on the class name, answered from the loader's RBS environment (or, for `class_known?`, the
40
+ # known-name set cached for it), fixed for the loader's lifetime. `@state` is not declared: beside its lazy
41
+ # derivations it holds warn-once flags and failure counters.
42
+ TypingState.memo_field(self, :@instance_definition_cache, :@singleton_definition_cache, :@class_known_cache,
43
+ reason: "keyed on the class name; a function of the loader's RBS environment")
44
+
38
45
  # Buffer name stamped on the `module` declarations synthesized by {.synthesize_missing_namespaces}.
39
46
  # Re-read off the built env by {#synthesized_namespaces} so the analysis layer can surface an `:info`
40
47
  # diagnostic naming the project's malformed-RBS namespaces — robust across the marshalled env cache,
@@ -63,13 +70,27 @@ module Rigor
63
70
  .compact.map { |root| "#{root}/".freeze }.freeze
64
71
  rescue StandardError
65
72
  [].freeze
66
- end
73
+ end.freeze
67
74
 
68
75
  # Cap on how many quarantined `signature_paths:` files {#warn_about_quarantined_signatures} lists by name
69
76
  # before collapsing the tail to "… and N more" — a broken generator can emit many, and a wall of parse
70
77
  # errors buries the signal.
71
78
  QUARANTINE_WARN_LIMIT = 10
72
79
 
80
+ # ADR-129 WD1 — a Frame: {.with_core_root} installs the core root for one environment build and restores the
81
+ # previous one on the way out. It changes answers (a loader built under it reads a different `core/`), and the
82
+ # installed value is a {CoreRoot.validate!}d frozen String or nil. Per thread (per fiber), so a concurrent build
83
+ # on another thread keeps its own.
84
+ CORE_ROOT = TypingState.frame(
85
+ :rbs_core_root,
86
+ reason: "installed per environment build by with_core_root; selects the core/ directory every loader of the " \
87
+ "build reads (a validated absolute path, or nil for the rbs gem's own)",
88
+ # G1′: the build installs the core root and reads it, and every analysing entry point builds the environment
89
+ # before anything types ({Environment.for_project}, `eager:`; #2883), so no `type_of` reads it.
90
+ typing_never_reads: true
91
+ )
92
+ private_constant :CORE_ROOT
93
+
73
94
  class << self
74
95
  # Issue #1902 — the fixed total order that picks ONE detail tuple for a class out of several: its two
75
96
  # sides in {#report_definition_build_failure}, or a fresh and a replayed record in
@@ -105,9 +126,15 @@ module Rigor
105
126
  # with it as an overload unless that source is the project's own ({DeferredSignaturePlan}).
106
127
  # @param core_root — issue #2517 — a validated {CoreRoot} directory loaded in place of the `rbs` gem's
107
128
  # `core/`, or nil for the default core.
108
- def build_env_for(libraries:, signature_paths:, virtual_rbs: [], deferred_signature_paths: [],
109
- project_signature_paths: [], core_root: nil)
110
- inputs = [signature_paths, virtual_rbs, deferred_signature_paths, project_signature_paths]
129
+ # @param subordinate_signature_paths — issue #2978 — the subset of `signature_paths` that loads right after
130
+ # the other `signature_paths` files and stands down member by member to them ({.add_project_signatures}).
131
+ # @param collection_signature_paths — issue #3030 — the subset of `signature_paths` that are `rbs collection`
132
+ # gem directories, read without their `_`-prefixed directories ({.project_sig_files}).
133
+ def build_env_for(libraries:, signature_paths:, virtual_rbs: [], deferred_signature_paths: [], # rubocop:disable Metrics/ParameterLists
134
+ project_signature_paths: [], core_root: nil, subordinate_signature_paths: [],
135
+ collection_signature_paths: [])
136
+ inputs = [signature_paths, virtual_rbs, deferred_signature_paths, project_signature_paths,
137
+ subordinate_signature_paths, collection_signature_paths]
111
138
  with_core_root(core_root) { build_env_from(libraries, inputs) }
112
139
  end
113
140
 
@@ -116,22 +143,17 @@ module Rigor
116
143
  # {.build_env_from} must read the same core, and a parameter missed on one of them would build that attempt
117
144
  # against the default core without a sign. Thread-local, so a concurrent build on another thread keeps its
118
145
  # own.
119
- def with_core_root(core_root)
120
- previous = Thread.current[:rigor_rbs_core_root]
121
- Thread.current[:rigor_rbs_core_root] = core_root
122
- yield
123
- ensure
124
- Thread.current[:rigor_rbs_core_root] = previous
146
+ def with_core_root(core_root, &)
147
+ TypingState.current.with(CORE_ROOT, core_root, &)
125
148
  end
126
149
 
127
150
  def build_env_from(libraries, inputs)
128
- signature_paths, virtual_rbs = inputs
129
151
  env, gated_dirs = whole_gated_attempt(libraries, inputs)
130
152
  return env if gated_dirs.empty?
131
153
 
132
154
  # Issue #1700 — a {RequiredFeatures}-gated directory joined; keep it whole only if it does not cost the
133
155
  # environment anything ({.gated_failures}).
134
- failed, quarantined = env ? gated_failures(env, gated_dirs, signature_paths, virtual_rbs) : [[], 1]
156
+ failed, quarantined = env ? gated_failures(env, gated_dirs, inputs) : [[], 1]
135
157
  return env if failed.empty? && quarantined.zero?
136
158
 
137
159
  tokens = gated_dirs.map { |dir| RequiredFeatures.token(RequiredFeatures.feature_for_dir(dir)) }
@@ -142,7 +164,7 @@ module Rigor
142
164
  trouble = failed.size + quarantined
143
165
  return fallback if env.nil?
144
166
 
145
- gated_trouble(fallback, gated_dirs, signature_paths, virtual_rbs) < trouble ? fallback : env
167
+ gated_trouble(fallback, gated_dirs, inputs) < trouble ? fallback : env
146
168
  end
147
169
 
148
170
  # The first attempt, with every activated gated directory whole. rbs 3.x validates a class's generic
@@ -170,7 +192,7 @@ module Rigor
170
192
  plan = GatedSignaturePlan.build(roots, fallback)
171
193
  if plan.changed
172
194
  env = gated_plan_attempt(libraries, inputs, plan)
173
- failed, quarantined = env ? gated_failures(env, gated_dirs, inputs[0], inputs[1]) : [failed, 0]
195
+ failed, quarantined = env ? gated_failures(env, gated_dirs, inputs) : [failed, 0]
174
196
  return env if env && failed.empty? && quarantined.zero?
175
197
  end
176
198
  return nil if failed.empty?
@@ -179,7 +201,7 @@ module Rigor
179
201
  env = gated_plan_attempt(libraries, inputs, plan)
180
202
  return nil if env.nil?
181
203
 
182
- failed, quarantined = gated_failures(env, gated_dirs, inputs[0], inputs[1])
204
+ failed, quarantined = gated_failures(env, gated_dirs, inputs)
183
205
  env if failed.empty? && quarantined.zero?
184
206
  end
185
207
 
@@ -218,8 +240,9 @@ module Rigor
218
240
  # One {.build_env_attempt} round, which also returns its {VendoredSignaturePlan}.
219
241
  #
220
242
  # @param vendored_excluded — signature files the vendored plan is made without.
221
- def planned_env_attempt(libraries, signature_paths, virtual_rbs, deferred_signature_paths,
222
- project_signature_paths = [], gated_plan: nil, vendored_excluded: Set.new)
243
+ def planned_env_attempt(libraries, signature_paths, virtual_rbs, deferred_signature_paths, # rubocop:disable Metrics/ParameterLists
244
+ project_signature_paths = [], subordinate_signature_paths = [],
245
+ collection_signature_paths = [], gated_plan: nil, vendored_excluded: Set.new)
223
246
  libraries = libraries_without_shadowed_bigdecimal_math(libraries)
224
247
  rbs_loader, loaded_libraries = library_loader(libraries)
225
248
  gated_dirs = loadable_gated_dirs(libraries, loaded_libraries)
@@ -229,7 +252,7 @@ module Rigor
229
252
  # type-of query then degrades to `Dynamic[top]` — the "sig looks harmful" failure of the 2026-07-06
230
253
  # mastodon coverage note). Per-file loading quarantines the broken file instead. Vendored / core-overlay
231
254
  # sigs are Rigor-shipped and trusted, so they stay on the loader's fast batch path.
232
- project_files = project_sig_files(signature_paths)
255
+ project_files = project_sig_files(signature_paths, collection_signature_paths)
233
256
  undeferred_files = project_files - project_sig_files(deferred_signature_paths)
234
257
  env, vendored_plan = bundled_env(rbs_loader, loaded_libraries, gated_plan ? [] : gated_dirs,
235
258
  undeferred_files, vendored_excluded)
@@ -240,7 +263,9 @@ module Rigor
240
263
  # inline-vs-`.rbs` consistency rule, and stand down to an inline member like to any other source.
241
264
  consistency = member_consistency_for(undeferred_files, virtual_rbs)
242
265
  add_project_signatures(env, signature_paths, deferred_signature_paths,
243
- standdowns: consistency.signature_standdowns)
266
+ standdowns: consistency.signature_standdowns,
267
+ subordinate_paths: subordinate_signature_paths,
268
+ collection_paths: collection_signature_paths)
244
269
  gated_plan&.shells&.each { |source| add_parsed_decls(env, *source) }
245
270
  # Issue #928 — the capability-role catalog goes in AFTER the project's own signatures, per
246
271
  # declaration, so a project that already declares one of these interface names keeps its own
@@ -296,7 +321,7 @@ module Rigor
296
321
 
297
322
  # An `RBS::EnvironmentLoader` with every library of `libraries` that resolves added, and those libraries.
298
323
  def library_loader(libraries)
299
- core_root = Thread.current[:rigor_rbs_core_root]
324
+ core_root = TypingState.current[CORE_ROOT]
300
325
  rbs_loader = core_root ? RBS::EnvironmentLoader.new(core_root: Pathname(core_root)) : RBS::EnvironmentLoader.new
301
326
  # Issue #1700 — a `RequiredFeatures` token names a gated vendored directory, never an RBS library.
302
327
  loaded_libraries = libraries.select do |library|
@@ -320,14 +345,16 @@ module Rigor
320
345
  # rather than a comparison of declarations because it answers exactly what RBS itself decides — an
321
346
  # overload continuation (`| ...`), a superclass written relative or absolute, a type parameter — with no
322
347
  # second reading of the rules to drift. It runs only when a gated directory joined, on an env-cache miss.
323
- def gated_trouble(env, dirs, signature_paths, virtual_rbs)
324
- failed, quarantined = gated_failures(env, dirs, signature_paths, virtual_rbs)
348
+ def gated_trouble(env, dirs, inputs)
349
+ failed, quarantined = gated_failures(env, dirs, inputs)
325
350
  failed.size + quarantined
326
351
  end
327
352
 
328
353
  # {.gated_trouble} split in two: the gated type names whose definitions fail, and the number of quarantined
329
- # signature sources that declare one. Issue #1713 re-plans against the first.
330
- def gated_failures(env, dirs, signature_paths, virtual_rbs)
354
+ # signature sources that declare one. Issue #1713 re-plans against the first. `inputs` is
355
+ # {.build_env_for}'s list: the signature paths, the virtual RBS, and (last) the collection directories.
356
+ def gated_failures(env, dirs, inputs)
357
+ signature_paths, virtual_rbs = inputs
331
358
  type_names = gated_type_names(dirs)
332
359
  builder = RBS::DefinitionBuilder.new(env: env)
333
360
  failed = type_names.select do |type_name|
@@ -344,7 +371,8 @@ module Rigor
344
371
  true
345
372
  end
346
373
  declares = gated_declaration_pattern(type_names)
347
- quarantined = collision_quarantined_project_signatures(env, signature_paths).count do |file, _note|
374
+ collided = collision_quarantined_project_signatures(env, signature_paths, inputs[5] || [])
375
+ quarantined = collided.count do |file, _note|
348
376
  File.read(file).match?(declares)
349
377
  rescue SystemCallError, IOError, ArgumentError
350
378
  false
@@ -633,12 +661,22 @@ module Rigor
633
661
  # The absolute paths of every `.rbs` file under the project's `signature_paths:` (NOT vendored /
634
662
  # stdlib RBS — those are well-formed, so attempting to build them would only waste time). Used to
635
663
  # scope the referenced-type build sweep.
636
- def project_sig_files(signature_paths)
664
+ #
665
+ # @param collection_paths — issue #3030 — the entries of `signature_paths` that are `rbs collection` gem
666
+ # directories, read without the files below a `_`-prefixed directory, as rbs reads a collection gem
667
+ # ({RbsCollectionDiscovery.hidden_file?}). Compared by spelling, as the loader receives both lists from
668
+ # one `Environment.for_project` call.
669
+ def project_sig_files(signature_paths, collection_paths = [])
670
+ collection = collection_paths.to_set(&:to_s)
637
671
  signature_paths.flat_map do |path|
638
672
  path = Pathname(path) unless path.is_a?(Pathname)
639
673
  next [] unless path.directory?
640
674
 
641
- RootedGlob.glob(path, "**/*.rbs").map { |p| File.expand_path(p) }
675
+ files = RootedGlob.glob(path, "**/*.rbs")
676
+ if collection.include?(path.to_s)
677
+ files = files.reject { |file| RbsCollectionDiscovery.hidden_file?(path, file) }
678
+ end
679
+ files.map { |p| File.expand_path(p) }
642
680
  end.to_set
643
681
  end
644
682
 
@@ -656,9 +694,16 @@ module Rigor
656
694
  # @param standdowns — issue #1075 — `{signature_path => Set[[class, method, kind]]}`, the members an
657
695
  # inline declaration displaced because it was the more precise side of a consistent pair
658
696
  # ({MemberConsistency::Resolution#signature_standdowns}). They are removed before the file is added.
659
- def add_project_signatures(env, signature_paths, deferred_paths = [], standdowns: {})
660
- deferred = project_sig_files(deferred_paths)
661
- (project_sig_files(signature_paths) - deferred).sort.each do |file|
697
+ # @param subordinate_paths — issue #2978 — the subset loaded after the rest, planned against them as a deferred
698
+ # file is ({.add_deferred_signatures}) with every one of them as `own`: a member any of them declares stands
699
+ # down from the subordinate file instead of failing the class's definition build. A gem's `rbs collection`
700
+ # copy sits behind the gem's own `sig/` this way, so `Bar#a` in both binds the gem's, and `Bar#b` in the
701
+ # copy alone still loads.
702
+ def add_project_signatures(env, signature_paths, deferred_paths = [], standdowns: {}, subordinate_paths: [],
703
+ collection_paths: [])
704
+ subordinate = project_sig_files(subordinate_paths, collection_paths) - project_sig_files(deferred_paths)
705
+ eager = project_sig_files(signature_paths, collection_paths) - project_sig_files(deferred_paths) - subordinate
706
+ eager.sort.each do |file|
662
707
  parsed = parse_signature_file(file)
663
708
  next if parsed.nil? # quarantined (unparseable) or unreadable — skip so the env survives
664
709
 
@@ -666,6 +711,7 @@ module Rigor
666
711
  strip_members!(decls, standdowns[file]) if standdowns.key?(file)
667
712
  add_project_parsed_decls(env, buffer, directives, decls)
668
713
  end
714
+ add_deferred_signatures(env, subordinate, eager, standdowns: standdowns)
669
715
  end
670
716
 
671
717
  # Issue #777 — add one project signature transactionally. `RBS::Environment#add_source` appends to
@@ -711,12 +757,15 @@ module Rigor
711
757
  # source's `def` as an overload continuation, or stands down when the other source is the project's own
712
758
  # (`own`, buffer names) or cannot be continued; a colliding declaration stands down on its own; the rest
713
759
  # of the file loads. {.deferred_member_standdowns} reads what stood down back off the built env.
714
- def add_deferred_signatures(env, deferred_files, own = Set.new)
760
+ #
761
+ # @param standdowns — issue #2978 — {.add_project_signatures}' members to remove before a file is planned.
762
+ def add_deferred_signatures(env, deferred_files, own = Set.new, standdowns: {})
715
763
  deferred_files.sort.each do |file|
716
764
  parsed = parse_signature_file(file)
717
765
  next if parsed.nil?
718
766
 
719
767
  buffer, directives, decls = parsed
768
+ strip_members!(decls, standdowns[file]) if standdowns.key?(file)
720
769
  next if generic_arity_conflict(env, decls)
721
770
 
722
771
  add_project_parsed_decls(env, buffer, directives, DeferredSignaturePlan.plan(decls, env, own))
@@ -844,8 +893,8 @@ module Rigor
844
893
  # {.add_project_signatures} so the warning fires even on a cache hit (where the env was already built
845
894
  # with the file quarantined). Cheap: it only re-parses the user's own (usually small) `sig/` set, and
846
895
  # returns empty immediately when there is no `signature_paths:`.
847
- def quarantined_project_signatures(signature_paths)
848
- project_sig_files(signature_paths).sort.filter_map do |file|
896
+ def quarantined_project_signatures(signature_paths, collection_paths = [])
897
+ project_sig_files(signature_paths, collection_paths).sort.filter_map do |file|
849
898
  # The note carries the path itself because the warn composer prints only this element — a
850
899
  # `ParsingError` message embeds its `path:line:` prefix, so the composer never adds one.
851
900
  content = File.read(file, encoding: "UTF-8")
@@ -876,7 +925,7 @@ module Rigor
876
925
  COLLISION_QUARANTINE_NOTE =
877
926
  "duplicated declaration against bundled RBS — quarantined so the rest of the RBS env still loads"
878
927
 
879
- def collision_quarantined_project_signatures(env, signature_paths)
928
+ def collision_quarantined_project_signatures(env, signature_paths, collection_paths = [])
880
929
  return [] if env.nil?
881
930
 
882
931
  present = env.buffers.to_set do |buffer|
@@ -888,7 +937,7 @@ module Rigor
888
937
  name.to_s
889
938
  end
890
939
  buffers_by_basename = env.buffers.group_by { |buffer| File.basename(buffer.name.to_s) }
891
- project_sig_files(signature_paths).sort.filter_map do |file|
940
+ project_sig_files(signature_paths, collection_paths).sort.filter_map do |file|
892
941
  next if present.include?(file)
893
942
  next if parse_signature_file(file).nil? # parse / encoding quarantine owns these
894
943
  next if project_signature_loaded_under_other_path?(file, buffers_by_basename)
@@ -1536,7 +1585,8 @@ module Rigor
1536
1585
  # add self-contained declarations) and stay unconditional.
1537
1586
  LIBRARY_SUPPLEMENT_CORE_OVERLAYS = {
1538
1587
  "resolv.rbs" => "resolv",
1539
- "string_scanner.rbs" => "strscan"
1588
+ "string_scanner.rbs" => "strscan",
1589
+ "bigdecimal.rbs" => "bigdecimal"
1540
1590
  }.freeze
1541
1591
  private_constant :LIBRARY_SUPPLEMENT_CORE_OVERLAYS
1542
1592
 
@@ -1637,17 +1687,19 @@ module Rigor
1637
1687
  end
1638
1688
 
1639
1689
  # Rigor-owned per-gem RBS overlays (`data/gem_overlay/<gem>/`), ADR-72. Unlike the unconditional
1640
- # `core_overlay`, each gem's overlay is loaded ONLY when that gem is locked in the project's
1641
- # Gemfile.lock but ships no RBS of its own — {Environment.for_project} decides eligibility and passes
1642
- # the already-filtered gem-name set here. One directory per gem name keeps the membership check a
1643
- # cheap `File.directory?`.
1690
+ # `core_overlay`, each gem's overlay is loaded only when that gem is locked in the project's
1691
+ # Gemfile.lock AND {Environment.for_project} finds its coverage source overlay-eligible
1692
+ # ({Environment::GEM_OVERLAY_COVERAGE_SOURCES} — `:missing` for every overlay but addressable's
1693
+ # `:rbs_collection` correction, #2857) with no reachable plugin twin; this method receives the
1694
+ # already-filtered gem-name set. One directory per gem name keeps the membership check a cheap
1695
+ # `File.directory?`.
1644
1696
  GEM_OVERLAY_SIGS_ROOT = File.expand_path(
1645
1697
  "../../../data/gem_overlay",
1646
1698
  __dir__
1647
1699
  ).freeze
1648
1700
 
1649
- # @param gem_names — overlay-eligible Gemfile.lock gem names (the caller filters
1650
- # to the `:missing`-coverage, no-conflicting-plugin set).
1701
+ # @param gem_names — overlay-eligible Gemfile.lock gem names (the caller filters to the
1702
+ # coverage-source-eligible, no-conflicting-plugin set).
1651
1703
  # @return the bundled overlay directory for each gem that ships one; empty when
1652
1704
  # none match or the overlay root is absent.
1653
1705
  def gem_overlay_sig_paths(gem_names)
@@ -1661,13 +1713,16 @@ module Rigor
1661
1713
 
1662
1714
  # @param path — typically an entry from an {RbsLoader} instance's
1663
1715
  # {#signature_paths}.
1664
- # @return whether `path` sits under the bundled gem-overlay root — what
1665
- # `CheckRules::GEM_OVERLAY_OPEN_RECEIVERS`'s gate consults so that a class name's membership in
1666
- # that list alone never grants the open-receiver exemption; the overlay directory that made the
1667
- # entry true must have actually loaded THIS run too (issue #632, tracked further by #660).
1668
- # `GEM_OVERLAY_SIGS_ROOT` is defined inside this `class << self` block, so it is reachable from
1669
- # here directly but NOT as `RbsLoader::GEM_OVERLAY_SIGS_ROOT` from outside — this method is the
1670
- # public seam callers outside this class use instead of reaching for the constant themselves.
1716
+ # @return whether `path` sits under the bundled gem-overlay root — the prefix half of
1717
+ # "this run loaded a `data/gem_overlay/` directory". Callers needing ONE gem's overlay ask
1718
+ # {.gem_overlay_sig_paths} for that gem's directory instead: `signature_standdowns` uses this
1719
+ # to recognise overlay rows across every gem, while `CheckRules#gem_overlay_loaded?` keys on
1720
+ # the activesupport overlay alone (#3041 — a project can load the addressable overlay without
1721
+ # the activesupport one, and only the latter may open `ActiveSupport::Duration` /
1722
+ # `ActiveSupport::Concern`). `GEM_OVERLAY_SIGS_ROOT` is defined inside this `class << self`
1723
+ # block, so it is reachable from here directly but NOT as `RbsLoader::GEM_OVERLAY_SIGS_ROOT`
1724
+ # from outside — this method is the public seam callers outside this class use instead of
1725
+ # reaching for the constant themselves.
1671
1726
  def under_gem_overlay_root?(path)
1672
1727
  path.to_s.start_with?("#{GEM_OVERLAY_SIGS_ROOT}/")
1673
1728
  end
@@ -1783,7 +1838,7 @@ module Rigor
1783
1838
  end
1784
1839
 
1785
1840
  attr_reader :libraries, :signature_paths, :cache_store, :virtual_rbs, :deferred_signature_paths,
1786
- :project_signature_paths, :core_root
1841
+ :project_signature_paths, :core_root, :subordinate_signature_paths, :collection_signature_paths
1787
1842
 
1788
1843
  # @param libraries — stdlib library names to load on top of core (e.g.,
1789
1844
  # `["pathname", "json"]`). Empty by default. Each entry MUST correspond to a directory under the
@@ -1806,17 +1861,20 @@ module Rigor
1806
1861
  # a miss — a list it cannot read is a stand-down that runs only on a `--no-cache` build.
1807
1862
  # @param core_root — issue #2517 — a directory loaded in place of the `rbs` gem's `core/`, or nil (the
1808
1863
  # default) for that core. Validated here, so a bad selection fails at construction ({CoreRoot.validate!}).
1864
+ # @param subordinate_signature_paths — issue #2978 — the subset of `signature_paths:` that stands down member by
1865
+ # member to the rest of it (a bundle gem's `rbs collection` copy, behind the gem's own `sig/`).
1866
+ # @param collection_signature_paths — issue #3030 — the subset of `signature_paths:` that are `rbs collection`
1867
+ # gem directories, read without the files below their `_`-prefixed directories ({.project_sig_files}).
1809
1868
  def initialize(libraries: [], signature_paths: [], cache_store: nil, virtual_rbs: [], # rubocop:disable Metrics/ParameterLists
1810
- deferred_signature_paths: [], project_signature_paths: [], required_features: [], core_root: nil)
1869
+ deferred_signature_paths: [], project_signature_paths: [], required_features: [], core_root: nil,
1870
+ subordinate_signature_paths: [], collection_signature_paths: [])
1811
1871
  @core_root = core_root.nil? ? nil : CoreRoot.validate!(core_root)
1812
1872
  CoreRoot.reject_overlap!(@core_root, project_signature_paths) if @core_root
1813
1873
  @libraries = libraries.map(&:to_s).freeze
1814
1874
  @signature_paths = signature_paths.map { |p| Pathname(p) }.freeze
1815
1875
  assign_reopen_inputs(project_signature_paths, required_features)
1816
- # Issue #610 — the subset of `signature_paths:` a bundled PLUGIN contributed. Loaded last and
1817
- # allowed to stand down against a colliding generic arity ({.add_deferred_signatures}). Every entry
1818
- # is also in `@signature_paths`, so the env cache descriptor already digests these files.
1819
- @deferred_signature_paths = deferred_signature_paths.map { |p| Pathname(p) }.freeze
1876
+ assign_partition(deferred_signature_paths, subordinate_signature_paths)
1877
+ @collection_signature_paths = collection_signature_paths.map { |p| Pathname(p) }.freeze
1820
1878
  @cache_store = cache_store
1821
1879
  @virtual_rbs = virtual_rbs.map { |name, content| [name.to_s.dup.freeze, content.to_s.dup.freeze].freeze }.freeze
1822
1880
  # Per-loader memoization bucket. Held as a single mutable Hash so the loader instance itself can be
@@ -1857,9 +1915,9 @@ module Rigor
1857
1915
  # @return empty when every `signature_paths:` file parses.
1858
1916
  def quarantined_signatures
1859
1917
  @state[:quarantined] ||= begin
1860
- parse_quarantined = self.class.quarantined_project_signatures(@signature_paths)
1918
+ parse_quarantined = self.class.quarantined_project_signatures(@signature_paths, @collection_signature_paths)
1861
1919
  collision_quarantined = self.class.collision_quarantined_project_signatures(
1862
- @state[:env], @signature_paths
1920
+ @state[:env], @signature_paths, @collection_signature_paths
1863
1921
  )
1864
1922
  parse_paths = parse_quarantined.to_set { |path, _note| path }
1865
1923
  # Issue #610 — a deferred file that STOOD DOWN is absent from the env by design, which is exactly
@@ -1883,11 +1941,14 @@ module Rigor
1883
1941
  #
1884
1942
  # Issue #2422 — followed by the members a deferred file that did load left out because another source
1885
1943
  # declares them ({.deferred_member_standdowns}), one row each.
1944
+ #
1945
+ # Issue #2974 — a gem overlay's rows are left out: the overlay is Rigor's own default, deferred so it stands
1946
+ # down to any source that declares the same thing, and that is not news to report (#2470's reasoning).
1886
1947
  def signature_standdowns
1887
1948
  @state[:signature_standdowns] ||= (
1888
1949
  self.class.deferred_standdowns(env, @deferred_signature_paths) +
1889
1950
  self.class.deferred_member_standdowns(env, @deferred_signature_paths)
1890
- ).freeze
1951
+ ).reject { |row| self.class.under_gem_overlay_root?(row.first) }.freeze
1891
1952
  end
1892
1953
 
1893
1954
  # Issue #1713 — what stood down from each {RequiredFeatures}-gated vendored directory this loader's library
@@ -1906,7 +1967,7 @@ module Rigor
1906
1967
  end
1907
1968
  return [] if roots.empty?
1908
1969
 
1909
- GatedSignaturePlan.standdowns(env, roots, self.class.project_sig_files(@signature_paths))
1970
+ GatedSignaturePlan.standdowns(env, roots, signature_files)
1910
1971
  end
1911
1972
  private :derive_vendored_standdowns
1912
1973
 
@@ -1942,7 +2003,7 @@ module Rigor
1942
2003
  environment = env
1943
2004
  return Set.new.freeze if environment.nil?
1944
2005
 
1945
- project_files = self.class.project_sig_files(@signature_paths)
2006
+ project_files = signature_files
1946
2007
  virtual_names = @virtual_rbs.to_set(&:first)
1947
2008
  return Set.new.freeze if project_files.empty? && virtual_names.empty?
1948
2009
 
@@ -2012,6 +2073,22 @@ module Rigor
2012
2073
  # Issue #2174 — the subset of `signature_paths:` the user configured (every entry is also in
2013
2074
  # `@signature_paths`), and the features the project's source requires: read only by
2014
2075
  # {#ruby_library_reopen?}.
2076
+ # Issue #610 — the subset of `signature_paths:` a bundled PLUGIN contributed. Loaded last and allowed to stand
2077
+ # down against a colliding generic arity ({.add_deferred_signatures}). Issue #2978 — and the subset that stands
2078
+ # down member by member to the rest. Every entry of either is also in `@signature_paths`, so the env cache
2079
+ # descriptor already digests these files.
2080
+ # Issue #3030 — every `.rbs` file this loader reads from its `signature_paths:`, the `_`-prefixed directories of
2081
+ # its `rbs collection` gem directories left out ({.project_sig_files}).
2082
+ def signature_files
2083
+ self.class.project_sig_files(@signature_paths, @collection_signature_paths)
2084
+ end
2085
+ private :signature_files
2086
+
2087
+ def assign_partition(deferred_signature_paths, subordinate_signature_paths)
2088
+ @deferred_signature_paths = deferred_signature_paths.map { |p| Pathname(p) }.freeze
2089
+ @subordinate_signature_paths = subordinate_signature_paths.map { |p| Pathname(p) }.freeze
2090
+ end
2091
+
2015
2092
  def assign_reopen_inputs(project_signature_paths, required_features)
2016
2093
  @project_signature_paths = project_signature_paths.map { |p| Pathname(p) }.freeze
2017
2094
  @required_features = required_features.map { |feature| feature.to_s.dup.freeze }.freeze
@@ -2147,7 +2224,7 @@ module Rigor
2147
2224
  [::RBS::EnvironmentLoader::DEFAULT_CORE_ROOT].compact.map { |root| "#{root}/".freeze }.freeze
2148
2225
  rescue StandardError
2149
2226
  [].freeze
2150
- end
2227
+ end.freeze
2151
2228
  private_constant :CORE_ROOTS
2152
2229
 
2153
2230
  def build_core_stdlib_classes(roots = core_stdlib_roots)
@@ -2174,6 +2251,96 @@ module Rigor
2174
2251
  core_stdlib_classes.include?(class_name.to_s.delete_prefix("::"))
2175
2252
  end
2176
2253
 
2254
+ # Issue #2884 — true when every declaration of `method_definition` (an `RBS::Definition::Method`) is in a named
2255
+ # buffer outside the `rbs` gem's `core/` and `stdlib/` trees: a project, gem or collection signature wrote it.
2256
+ # Attributed by buffer name, as {#core_or_stdlib_class?} is; a declaration whose buffer name is lost (an old
2257
+ # environment blob's `<cached>` sentinel) answers false, which switches the caller's feature off.
2258
+ def declared_outside_core_stdlib?(method_definition)
2259
+ roots = core_stdlib_roots
2260
+ type_defs = method_definition.defs
2261
+ return false if roots.empty? || type_defs.empty?
2262
+
2263
+ type_defs.all? do |type_def|
2264
+ name = self.class.declaration_buffer_name(type_def.member)
2265
+ !name.nil? && roots.none? { |root| name.start_with?(root) }
2266
+ end
2267
+ end
2268
+
2269
+ # Issue #2884 — true when `method_definition`, a class's singleton `new`, is one `X.new` answers through: every
2270
+ # definition is an explicit `def self.new` (not the `new` `RBS::DefinitionBuilder` derives from `initialize`,
2271
+ # which returns the declaring class), written outside core / stdlib ({#declared_outside_core_stdlib?}), on a
2272
+ # class core / stdlib does not declare (a project signature reopening `String` does not change `String.new`).
2273
+ def honoured_singleton_new?(method_definition)
2274
+ method_definition.defs.all? { |type_def| explicit_singleton_new_member?(type_def.member) } &&
2275
+ declared_outside_core_stdlib?(method_definition) &&
2276
+ !core_or_stdlib_class?(method_definition.defined_in.to_s)
2277
+ end
2278
+
2279
+ def explicit_singleton_new_member?(member)
2280
+ member.is_a?(::RBS::AST::Members::MethodDefinition) && member.name == :new && member.kind != :instance
2281
+ end
2282
+ private :explicit_singleton_new_member?
2283
+
2284
+ # Issue #2884 — true when `method_definition`, a singleton `new`, answers what `Nominal[receiver]` answers for
2285
+ # every receiver that inherits it: each overload returns `instance`, untyped, `top`, `bot` or `void`, or the
2286
+ # declaring class itself, compared by its resolved full name. `self` is the receiver's class object, not its
2287
+ # instance, so it does not count. The dispatcher answers `Nominal[receiver]` for an inherited declaration this
2288
+ # holds of, and {#singleton_new_departs_from_instance?} is built from the same test, so the two cannot drift.
2289
+ def answers_instance_for_subclass?(method_definition)
2290
+ owner = method_definition.defined_in.to_s.delete_prefix("::")
2291
+ method_definition.method_types.all? do |method_type|
2292
+ returned = method_type.type.return_type
2293
+ INSTANCE_LIKE_RETURNS.any? { |base| returned.is_a?(base) } ||
2294
+ (returned.is_a?(::RBS::Types::ClassInstance) && returned.name.to_s.delete_prefix("::") == owner)
2295
+ end
2296
+ end
2297
+
2298
+ INSTANCE_LIKE_RETURNS = [
2299
+ ::RBS::Types::Bases::Instance, ::RBS::Types::Bases::Any, ::RBS::Types::Bases::Top,
2300
+ ::RBS::Types::Bases::Bottom, ::RBS::Types::Bases::Void
2301
+ ].freeze
2302
+ private_constant :INSTANCE_LIKE_RETURNS
2303
+
2304
+ # Issue #2884 — true when some class declares a {#honoured_singleton_new?} `new` that is not
2305
+ # {#answers_instance_for_subclass?}: only such a declaration answers a Ruby subclass with something other than
2306
+ # the `Nominal[subclass]` the dispatcher gives without one, so the dispatcher walks a subclass's ancestry, and
2307
+ # files that walk's dependency edges, only when this holds. Memoised per loader: one pass over `class_decls` for
2308
+ # the classes whose own non-core declaration writes `def self.new`, then their definitions.
2309
+ def singleton_new_departs_from_instance?
2310
+ return @state[:singleton_new_departs] if @state.key?(:singleton_new_departs)
2311
+
2312
+ @state[:singleton_new_departs] = scan_singleton_new_departs
2313
+ end
2314
+
2315
+ def scan_singleton_new_departs
2316
+ during_internal_demand do
2317
+ singleton_new_declarers.any? do |class_name|
2318
+ definition = singleton_method(class_name: class_name, method_name: :new)
2319
+ definition && honoured_singleton_new?(definition) && !answers_instance_for_subclass?(definition)
2320
+ end
2321
+ end
2322
+ rescue ::RBS::BaseError
2323
+ true
2324
+ end
2325
+ private :scan_singleton_new_departs
2326
+
2327
+ def singleton_new_declarers
2328
+ environment = env
2329
+ roots = core_stdlib_roots
2330
+ return [] if environment.nil? || roots.empty?
2331
+
2332
+ environment.class_decls.filter_map do |type_name, entry|
2333
+ declares = self.class.entry_declarations(entry).any? do |decl|
2334
+ name = self.class.declaration_buffer_name(decl)
2335
+ next false if name.nil? || roots.any? { |root| name.start_with?(root) }
2336
+
2337
+ decl.members.any? { |member| explicit_singleton_new_member?(member) }
2338
+ end
2339
+ type_name.to_s if declares
2340
+ end
2341
+ end
2342
+ private :singleton_new_declarers
2343
+
2177
2344
  # True when `class_name`'s RBS declaration is one the project wrote (see {#project_declared_classes}).
2178
2345
  def project_declared_class?(class_name)
2179
2346
  project_declared_classes.include?(class_name.to_s.delete_prefix("::"))
@@ -2241,6 +2408,13 @@ module Rigor
2241
2408
  nil
2242
2409
  end
2243
2410
 
2411
+ # Issue #2883 — builds the environment now, for the default core as {#verify_core_root!} does for a selected one,
2412
+ # so no `type_of` builds it lazily (ADR-129 G1': the core-root Frame the build reads is never read inside typing).
2413
+ def prepare_for_typing!
2414
+ env
2415
+ nil
2416
+ end
2417
+
2244
2418
  # Issue #696 — the PER-CLASS sibling of {#env_build_failure}, one tier quieter in consequence: an
2245
2419
  # `RBS::DefinitionBuilder` failure ({#build_instance_definition} / {#build_singleton_definition}'s
2246
2420
  # rescue) leaves the class KNOWN but with no method surface, so every call on it — real methods and
@@ -2326,7 +2500,7 @@ module Rigor
2326
2500
  def member_consistency(proof: nil)
2327
2501
  key = proof ? :member_consistency_proven : :member_consistency
2328
2502
  @state[key] ||= self.class.member_consistency_for(
2329
- self.class.project_sig_files(@signature_paths) - self.class.project_sig_files(@deferred_signature_paths),
2503
+ signature_files - self.class.project_sig_files(@deferred_signature_paths),
2330
2504
  @virtual_rbs, proof: proof
2331
2505
  ).records
2332
2506
  end
@@ -2455,6 +2629,51 @@ module Rigor
2455
2629
  end
2456
2630
  private :mixin_parent
2457
2631
 
2632
+ # Issue #2681 — every method name declared, on either side, by `name`'s RBS declarations and by the classes and
2633
+ # modules they inherit, include, prepend or extend, `::`-stripped names in `skip` excepted. Read from the
2634
+ # declarations as {#declared_ancestry} reads them, not through the definition builder: a definition that fails to
2635
+ # build reports a coverage diagnostic each time it is asked for. A superset of what the built definitions hold
2636
+ # is harmless to the one reader, a gate in front of ADR-43's override walk. Empty when the environment is
2637
+ # unavailable; the walk stops at 64 owners.
2638
+ def declared_method_names(name, skip: [])
2639
+ return Set.new if env.nil?
2640
+
2641
+ names = Set.new
2642
+ pending = [name.to_s.delete_prefix("::")]
2643
+ seen = Set.new
2644
+ until pending.empty? || seen.size > 64
2645
+ owner = pending.shift
2646
+ next if seen.include?(owner) || skip.include?(owner)
2647
+
2648
+ seen << owner
2649
+ entry = env.class_decls[parse_type_name(owner)]
2650
+ self.class.entry_declarations(entry).each { |decl| read_declared_methods(decl, names, pending) } if entry
2651
+ end
2652
+ names
2653
+ rescue ::RBS::BaseError
2654
+ Set.new
2655
+ end
2656
+
2657
+ def read_declared_methods(decl, names, pending)
2658
+ superclass = decl.respond_to?(:super_class) ? decl.super_class : nil
2659
+ pending << superclass.name.to_s.delete_prefix("::") if superclass
2660
+ return unless decl.respond_to?(:members)
2661
+
2662
+ decl.members.each do |member|
2663
+ case member
2664
+ when ::RBS::AST::Members::MethodDefinition, ::RBS::AST::Members::AttrReader
2665
+ names << member.name
2666
+ when ::RBS::AST::Members::AttrAccessor, ::RBS::AST::Members::AttrWriter
2667
+ names << member.name << :"#{member.name}="
2668
+ when ::RBS::AST::Members::Alias
2669
+ names << member.new_name
2670
+ when ::RBS::AST::Members::Include, ::RBS::AST::Members::Prepend, ::RBS::AST::Members::Extend
2671
+ pending << member.name.to_s.delete_prefix("::") unless member.name.interface?
2672
+ end
2673
+ end
2674
+ end
2675
+ private :read_declared_methods
2676
+
2458
2677
  # Issue #2321 — every class name an RBS class declaration names as its superclass, `::`-stripped: a class
2459
2678
  # absent from the set has no RBS-declared subclass. `nil` when the environment is unavailable or fails to read,
2460
2679
  # so a caller cannot mistake an unread environment for one without subclasses. Memoised per loader.
@@ -2675,6 +2894,34 @@ module Rigor
2675
2894
  nil
2676
2895
  end
2677
2896
 
2897
+ # #775 — the memo {Inference::RbsTypeTranslator} keeps, per loader, for the translated form of a closed alias
2898
+ # expansion (keyed by the expansion). It lives here because the translation reads this loader's environment
2899
+ # through any alias nested in the expansion ({#expand_type_alias}): another loader's `b` may differ under an
2900
+ # equal `a`, and the entry goes with the loader. The translator is its only reader and writer.
2901
+ def closed_alias_translations
2902
+ @state[:closed_alias_translations] ||= {}
2903
+ end
2904
+
2905
+ # An independent resolver for a read-only consumer. Its memo belongs to the caller, never the loader cache.
2906
+ def type_name_resolver
2907
+ environment = env
2908
+ return nil if environment.nil?
2909
+
2910
+ klass = ::RBS::Resolver::TypeNameResolver
2911
+ if klass.respond_to?(:build)
2912
+ klass.build(environment)
2913
+ else
2914
+ # Older RBS 3.x takes the environment instead of the current constructor's name tables.
2915
+ klass.new(environment) # rigor:disable call.wrong-arity
2916
+ end
2917
+ end
2918
+
2919
+ # The resolved alias declaration, without substituting type arguments or translating its AST.
2920
+ def type_alias_declaration(name)
2921
+ declarations = env&.type_alias_decls
2922
+ declarations&.[](name)&.decl
2923
+ end
2924
+
2678
2925
  # @return the resolved singleton (class object) definition for `class_name`. The
2679
2926
  # methods on this definition are the *class methods* of `class_name`, including those inherited from
2680
2927
  # `Class` and `Module` for class types. Returns nil for unknown names and on RBS build errors
@@ -3315,7 +3562,9 @@ module Rigor
3315
3562
  virtual_rbs: @virtual_rbs,
3316
3563
  deferred_signature_paths: @deferred_signature_paths,
3317
3564
  project_signature_paths: @project_signature_paths,
3318
- core_root: @core_root
3565
+ core_root: @core_root,
3566
+ subordinate_signature_paths: @subordinate_signature_paths,
3567
+ collection_signature_paths: @collection_signature_paths
3319
3568
  )
3320
3569
  end
3321
3570