rigortype 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/builtins/ruby_core/io.yml +1 -0
  4. data/data/builtins/ruby_core/proc.yml +4 -0
  5. data/data/builtins/ruby_core/random.yml +4 -0
  6. data/data/builtins/ruby_core/struct.yml +3 -0
  7. data/data/core_overlay/env.rbs +34 -0
  8. data/data/core_overlay/gc.rbs +50 -0
  9. data/data/core_overlay/hash.rbs +45 -0
  10. data/data/core_overlay/integer.rbs +31 -0
  11. data/data/core_overlay/io_buffer.rbs +27 -0
  12. data/data/core_overlay/kernel.rbs +40 -0
  13. data/data/core_overlay/match_data.rbs +28 -0
  14. data/data/core_overlay/module.rbs +51 -0
  15. data/data/core_overlay/object_space.rbs +29 -0
  16. data/data/core_overlay/proc.rbs +45 -0
  17. data/data/core_overlay/range.rbs +40 -0
  18. data/data/core_overlay/source_range.rbs +78 -0
  19. data/data/core_overlay/string.rbs +86 -0
  20. data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
  21. data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
  22. data/docs/handbook/01-getting-started.md +3 -3
  23. data/docs/handbook/06-classes.md +3 -1
  24. data/docs/handbook/11-sig-gen.md +6 -0
  25. data/docs/handbook/appendix-liskov.md +5 -0
  26. data/docs/manual/03-configuration.md +1 -1
  27. data/docs/manual/04-diagnostics.md +2 -1
  28. data/docs/manual/06-baseline.md +2 -0
  29. data/docs/manual/07-plugins.md +1 -1
  30. data/docs/manual/18-removing-dead-code.md +32 -2
  31. data/docs/manual/plugins/README.md +6 -1
  32. data/docs/manual/plugins/rigor-activerecord.md +21 -0
  33. data/docs/manual/plugins/rigor-alba.md +60 -0
  34. data/docs/manual/plugins/rigor-minitest.md +22 -4
  35. data/docs/manual/plugins/rigor-typelizer.md +71 -0
  36. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
  37. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
  38. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
  39. data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
  40. data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
  41. data/lib/rigor/analysis/check_rules.rb +289 -57
  42. data/lib/rigor/analysis/dependency_recorder.rb +15 -0
  43. data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
  44. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
  45. data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
  46. data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
  47. data/lib/rigor/analysis/erb_template_detector.rb +2 -1
  48. data/lib/rigor/analysis/incremental.rb +29 -9
  49. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
  50. data/lib/rigor/analysis/incremental_run_slot.rb +3 -1
  51. data/lib/rigor/analysis/incremental_session.rb +14 -1
  52. data/lib/rigor/analysis/reachability/graph.rb +123 -23
  53. data/lib/rigor/analysis/reachability/scan.rb +290 -36
  54. data/lib/rigor/analysis/rule_catalog.rb +43 -0
  55. data/lib/rigor/analysis/run_cache_key.rb +16 -1
  56. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
  57. data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
  58. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  59. data/lib/rigor/analysis/runner.rb +34 -3
  60. data/lib/rigor/analysis/worker_session.rb +7 -4
  61. data/lib/rigor/cache/incremental_snapshot.rb +22 -0
  62. data/lib/rigor/cli/annotate_command.rb +5 -0
  63. data/lib/rigor/cli/baseline_command.rb +7 -18
  64. data/lib/rigor/cli/check_command.rb +2 -1
  65. data/lib/rigor/cli/trace_command.rb +5 -0
  66. data/lib/rigor/cli/type_of_command.rb +5 -3
  67. data/lib/rigor/cli/unused_command.rb +13 -7
  68. data/lib/rigor/cli.rb +10 -2
  69. data/lib/rigor/configuration/severity_profile.rb +6 -0
  70. data/lib/rigor/configuration.rb +46 -2
  71. data/lib/rigor/environment/gated_signature_guard.rb +43 -0
  72. data/lib/rigor/environment/gated_signature_plan.rb +339 -0
  73. data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
  74. data/lib/rigor/environment/rbs_loader.rb +283 -14
  75. data/lib/rigor/environment/required_features.rb +206 -0
  76. data/lib/rigor/environment.rb +21 -3
  77. data/lib/rigor/inference/acceptance.rb +28 -3
  78. data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
  79. data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
  80. data/lib/rigor/inference/def_node_resolver.rb +23 -1
  81. data/lib/rigor/inference/expression_typer.rb +215 -16
  82. data/lib/rigor/inference/global_write_census.rb +7 -4
  83. data/lib/rigor/inference/guard_rebinding.rb +1 -1
  84. data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
  85. data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
  86. data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
  87. data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
  88. data/lib/rigor/inference/in_effect_refinements.rb +256 -0
  89. data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
  90. data/lib/rigor/inference/key_presence_guard.rb +303 -0
  91. data/lib/rigor/inference/macro_block_self_type.rb +59 -12
  92. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  93. data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
  94. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
  95. data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
  96. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
  97. data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
  98. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
  99. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
  100. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +259 -28
  101. data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
  102. data/lib/rigor/inference/method_dispatcher.rb +44 -11
  103. data/lib/rigor/inference/module_function_state.rb +2 -1
  104. data/lib/rigor/inference/narrowing.rb +20 -6
  105. data/lib/rigor/inference/object_mixins.rb +180 -0
  106. data/lib/rigor/inference/project_patched_methods.rb +10 -5
  107. data/lib/rigor/inference/project_patched_scanner.rb +71 -3
  108. data/lib/rigor/inference/refine_census.rb +239 -0
  109. data/lib/rigor/inference/refine_self.rb +317 -0
  110. data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
  111. data/lib/rigor/inference/refined_dispatch.rb +272 -0
  112. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
  113. data/lib/rigor/inference/scope_indexer.rb +687 -60
  114. data/lib/rigor/inference/statement_evaluator.rb +71 -20
  115. data/lib/rigor/inference/string_mutation.rb +9 -3
  116. data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
  117. data/lib/rigor/inference/version_guard.rb +19 -10
  118. data/lib/rigor/language_server/project_context.rb +31 -0
  119. data/lib/rigor/language_server/server.rb +18 -0
  120. data/lib/rigor/plugin/inflector.rb +1 -1
  121. data/lib/rigor/plugin/io_boundary.rb +42 -3
  122. data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
  123. data/lib/rigor/project_environment.rb +29 -6
  124. data/lib/rigor/protection/mutation_cache.rb +1 -1
  125. data/lib/rigor/protection/mutator.rb +1 -1
  126. data/lib/rigor/reflection/constant_ancestors.rb +127 -11
  127. data/lib/rigor/reflection/constant_path.rb +13 -2
  128. data/lib/rigor/reflection.rb +40 -8
  129. data/lib/rigor/scope/discovery_index.rb +40 -1
  130. data/lib/rigor/scope/resolution_chain.rb +159 -11
  131. data/lib/rigor/scope.rb +189 -10
  132. data/lib/rigor/sig_gen/classification.rb +4 -1
  133. data/lib/rigor/sig_gen/generator.rb +55 -5
  134. data/lib/rigor/sig_gen/method_candidate.rb +2 -2
  135. data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
  136. data/lib/rigor/source/alias_names.rb +52 -0
  137. data/lib/rigor/source/literals.rb +3 -2
  138. data/lib/rigor/source/symbol_name.rb +20 -0
  139. data/lib/rigor/type/combinator.rb +34 -2
  140. data/lib/rigor/type/float_range.rb +4 -1
  141. data/lib/rigor/type/nominal.rb +6 -1
  142. data/lib/rigor/version.rb +1 -1
  143. data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
  144. data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
  145. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
  146. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
  147. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
  148. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
  149. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
  150. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
  151. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
  152. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
  153. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
  154. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
  155. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
  156. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
  157. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
  158. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
  159. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
  160. data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
  161. data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
  162. data/plugins/rigor-alba/sig/alba.rbs +4 -0
  163. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
  164. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
  165. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
  166. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
  167. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
  168. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
  169. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
  170. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
  171. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
  172. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
  173. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
  174. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
  175. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
  176. data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
  177. data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
  178. data/sig/rigor/plugin/io_boundary.rbs +3 -0
  179. data/sig/rigor/scope.rbs +27 -4
  180. data/sig/rigor/source.rbs +4 -0
  181. data/sig/rigor.rbs +3 -1
  182. metadata +66 -1
@@ -7,6 +7,9 @@ require_relative "../inference/rbs_type_translator"
7
7
  require_relative "../builtins/imported_refinements"
8
8
  require_relative "rbs_hierarchy"
9
9
  require_relative "member_consistency"
10
+ require_relative "required_features"
11
+ require_relative "gated_signature_guard"
12
+ require_relative "gated_signature_plan"
10
13
 
11
14
  module Rigor
12
15
  class Environment
@@ -83,28 +86,100 @@ module Rigor
83
86
  # per-gem RBS bundled with Rigor itself is in scope for every analysis run. The gem stubs are
84
87
  # intentionally read-only and appended LAST so user-supplied `signature_paths` win on name conflicts.
85
88
  def build_env_for(libraries:, signature_paths:, virtual_rbs: [], deferred_signature_paths: [])
86
- rbs_loader = RBS::EnvironmentLoader.new
87
- libraries = libraries_without_shadowed_bigdecimal_math(libraries)
88
- loaded_libraries = libraries.select do |library|
89
- rbs_loader.has_library?(library: library, version: nil)
90
- end
91
- loaded_libraries.each do |library|
92
- rbs_loader.add(library: library, version: nil)
89
+ inputs = [signature_paths, virtual_rbs, deferred_signature_paths]
90
+ env, gated_dirs = whole_gated_attempt(libraries, inputs)
91
+ return env if gated_dirs.empty?
92
+
93
+ # Issue #1700 — a {RequiredFeatures}-gated directory joined; keep it whole only if it does not cost the
94
+ # environment anything ({.gated_failures}).
95
+ failed, quarantined = env ? gated_failures(env, gated_dirs, signature_paths, virtual_rbs) : [[], 1]
96
+ return env if failed.empty? && quarantined.zero?
97
+
98
+ tokens = gated_dirs.map { |dir| RequiredFeatures.token(RequiredFeatures.feature_for_dir(dir)) }
99
+ fallback, = build_env_attempt(libraries.reject { |library| tokens.include?(library.to_s) }, *inputs)
100
+ partial = partial_gated_env(libraries, inputs, gated_dirs, fallback, failed)
101
+ return partial if partial
102
+
103
+ trouble = failed.size + quarantined
104
+ return fallback if env.nil?
105
+
106
+ gated_trouble(fallback, gated_dirs, signature_paths, virtual_rbs) < trouble ? fallback : env
107
+ end
108
+
109
+ # The first attempt, with every activated gated directory whole. rbs 3.x validates a class's generic
110
+ # parameters and kind as each declaration is inserted, so a project `class Prime[T]` or `module Prime`
111
+ # raises out of this build where rbs 4.x lets the trial find it; with a gated directory active that is
112
+ # the directory's cost, not the environment's, and the build answers nil for the planned builds to
113
+ # replace. Without one the error is the environment's own and propagates as before.
114
+ def whole_gated_attempt(libraries, inputs)
115
+ build_env_attempt(libraries, *inputs)
116
+ rescue ::RBS::BaseError
117
+ gated_dirs = loadable_gated_dirs(libraries, library_loader(libraries).last)
118
+ raise if gated_dirs.empty?
119
+
120
+ [nil, gated_dirs]
121
+ end
122
+
123
+ # Issue #1713 — the directory less what clashes ({GatedSignaturePlan}), planned against `fallback`, the
124
+ # environment without it. A plan whose trial build still fails for some gated type is re-planned once
125
+ # with each such type shelled — standing down as a whole — and nil is returned when that fails too, so the
126
+ # caller falls back to the whole directory standing down. Every input is one the env-cache key covers.
127
+ #
128
+ # @param failed — the gated types the whole directory failed to build for.
129
+ def partial_gated_env(libraries, inputs, gated_dirs, fallback, failed)
130
+ roots = gated_dirs.map { |dir| gated_dir_root(dir) }
131
+ plan = GatedSignaturePlan.build(roots, fallback)
132
+ if plan.changed
133
+ env = gated_plan_attempt(libraries, inputs, plan)
134
+ failed, quarantined = env ? gated_failures(env, gated_dirs, inputs[0], inputs[1]) : [failed, 0]
135
+ return env if env && failed.empty? && quarantined.zero?
93
136
  end
137
+ return nil if failed.empty?
138
+
139
+ plan = GatedSignaturePlan.build(roots, fallback, failed.to_set { |name| name.to_s.delete_prefix("::") })
140
+ env = gated_plan_attempt(libraries, inputs, plan)
141
+ return nil if env.nil?
142
+
143
+ failed, quarantined = gated_failures(env, gated_dirs, inputs[0], inputs[1])
144
+ env if failed.empty? && quarantined.zero?
145
+ end
146
+
147
+ # A planned build is a candidate, never a requirement: one that raises leaves the caller's fallback.
148
+ def gated_plan_attempt(libraries, inputs, plan)
149
+ build_env_attempt(libraries, *inputs, gated_plan: plan).first
150
+ rescue ::RBS::BaseError
151
+ nil
152
+ end
153
+
154
+ def gated_dir_root(dir)
155
+ Pathname(File.join(VENDORED_GEM_SIGS_ROOT, dir))
156
+ end
157
+
158
+ # One environment build ({.build_env_for}'s body before the issue #1700 trial). Returns the environment
159
+ # and the gated vendored directories it loaded.
160
+ #
161
+ # @param gated_plan — issue #1713 — a {GatedSignaturePlan} loaded in place of the gated directories: its
162
+ # kept declarations where the directories would have loaded, its shells after the project's signatures.
163
+ def build_env_attempt(libraries, signature_paths, virtual_rbs, deferred_signature_paths, gated_plan: nil)
164
+ libraries = libraries_without_shadowed_bigdecimal_math(libraries)
165
+ rbs_loader, loaded_libraries = library_loader(libraries)
166
+ gated_dirs = loadable_gated_dirs(libraries, loaded_libraries)
94
167
  # Project `signature_paths:` are loaded per-file by {.add_project_signatures} AFTER `from_loader`,
95
168
  # NOT added to the loader here: `RBS::Environment.from_loader` parses every added file all-or-nothing,
96
169
  # so one unparseable user `.rbs` raises `RBS::ParsingError` and collapses the WHOLE env to nil (every
97
170
  # type-of query then degrades to `Dynamic[top]` — the "sig looks harmful" failure of the 2026-07-06
98
171
  # mastodon coverage note). Per-file loading quarantines the broken file instead. Vendored / core-overlay
99
172
  # sigs are Rigor-shipped and trusted, so they stay on the loader's fast batch path.
100
- add_bundled_signatures(rbs_loader, loaded_libraries.to_set(&:to_s))
173
+ add_bundled_signatures(rbs_loader, loaded_libraries.to_set(&:to_s), gated_plan ? [] : gated_dirs)
101
174
  env = unload_upstream_core_shims(RBS::Environment.from_loader(rbs_loader), rbs_loader)
175
+ gated_plan&.leading&.each { |source| add_parsed_decls(env, *source) }
102
176
  project_files = project_sig_files(signature_paths)
103
177
  # Issue #1075 — decided from the inputs before either side enters `env`: the consistency rule may
104
178
  # stand the `.rbs` member down, and that has to happen before its file is added.
105
179
  consistency = member_consistency_for(project_files, virtual_rbs)
106
180
  add_project_signatures(env, signature_paths, deferred_signature_paths,
107
181
  standdowns: consistency.signature_standdowns)
182
+ gated_plan&.shells&.each { |source| add_parsed_decls(env, *source) }
108
183
  # Issue #928 — the capability-role catalog goes in AFTER the project's own signatures, per
109
184
  # declaration, so a project that already declares one of these interface names keeps its own
110
185
  # (`add_capability_role_signatures`). The rest of the bundled RBS cannot be ordered that way (it
@@ -119,7 +194,86 @@ module Rigor
119
194
  # Issue #777 — resolve-time backstop: also unload colliding PROJECT buffers (class-vs-module /
120
195
  # constant redeclarations against bundled RBS). Virtual culprits are preferred when both appear.
121
196
  env, resolved = resolve_quarantining_virtual_collisions(env, virtual_rbs, project_files: project_files)
122
- stub_missing_referenced_types(env, resolved, project_files)
197
+ [stub_missing_referenced_types(env, resolved, project_files), gated_dirs]
198
+ end
199
+
200
+ # An `RBS::EnvironmentLoader` with every library of `libraries` that resolves added, and those libraries.
201
+ def library_loader(libraries)
202
+ rbs_loader = RBS::EnvironmentLoader.new
203
+ # Issue #1700 — a `RequiredFeatures` token names a gated vendored directory, never an RBS library.
204
+ loaded_libraries = libraries.select do |library|
205
+ next false if RequiredFeatures.feature_of_token(library)
206
+
207
+ rbs_loader.has_library?(library: library, version: nil)
208
+ end
209
+ loaded_libraries.each do |library|
210
+ rbs_loader.add(library: library, version: nil)
211
+ end
212
+ [rbs_loader, loaded_libraries]
213
+ end
214
+
215
+ # Issue #1700 — what loading the gated vendored `dirs` costs `env`: each type they declare whose instance
216
+ # or singleton definition fails to build, plus each project signature file or inline-synthesized RBS
217
+ # buffer quarantined for a duplicate declaration. A vendored copy joins signatures the project chose — its
218
+ # own `sig/`, a `signature_paths:` entry, a gem's `sig/`, an `rbs collection` copy, plugin signatures,
219
+ # inline RBS — and any clash with them (a member both declare, a class one side declares as a module,
220
+ # a different superclass or generic arity) surfaces here as a failed definition or a quarantine, and
221
+ # degrades the whole class to `Dynamic[top]` or drops the project's file. A trial build is the oracle
222
+ # rather than a comparison of declarations because it answers exactly what RBS itself decides — an
223
+ # overload continuation (`| ...`), a superclass written relative or absolute, a type parameter — with no
224
+ # second reading of the rules to drift. It runs only when a gated directory joined, on an env-cache miss.
225
+ def gated_trouble(env, dirs, signature_paths, virtual_rbs)
226
+ failed, quarantined = gated_failures(env, dirs, signature_paths, virtual_rbs)
227
+ failed.size + quarantined
228
+ end
229
+
230
+ # {.gated_trouble} split in two: the gated type names whose definitions fail, and the number of quarantined
231
+ # signature sources that declare one. Issue #1713 re-plans against the first.
232
+ def gated_failures(env, dirs, signature_paths, virtual_rbs)
233
+ type_names = gated_type_names(dirs)
234
+ builder = RBS::DefinitionBuilder.new(env: env)
235
+ failed = type_names.select do |type_name|
236
+ entry = env.class_decls[type_name]
237
+ next false if entry.nil?
238
+
239
+ # rbs 4 validates the declarations' type parameters here (`GenericParameterMismatchError`), which
240
+ # the definition builds below do not reach on their own.
241
+ entry.type_params if entry.respond_to?(:type_params)
242
+ builder.build_instance(type_name)
243
+ builder.build_singleton(type_name)
244
+ false
245
+ rescue StandardError
246
+ true
247
+ end
248
+ declares = gated_declaration_pattern(type_names)
249
+ quarantined = collision_quarantined_project_signatures(env, signature_paths).count do |file, _note|
250
+ File.read(file).match?(declares)
251
+ rescue SystemCallError, IOError, ArgumentError
252
+ false
253
+ end
254
+ [failed, quarantined + quarantined_virtual_count(env, virtual_rbs, declares)]
255
+ end
256
+
257
+ # A `class` / `module` line declaring one of the gated types, by its last name segment. A quarantine is
258
+ # counted only for a file that declares one, so a project whose other signature files were already
259
+ # quarantined (a duplicated gem `sig/`) does not read every gated directory as costly and pay a second
260
+ # build on each env-cache miss.
261
+ def gated_declaration_pattern(type_names)
262
+ segments = type_names.map { |name| Regexp.escape(name.name.to_s) }.uniq.join("|")
263
+ /^\s*(?:class|module)\s+(?:::)?(?:[\w:]*::)?(?:#{segments})\b/
264
+ end
265
+
266
+ def gated_type_names(dirs)
267
+ roots = dirs.map { |dir| gated_dir_root(dir) }
268
+ names = roots.flat_map { |root| GatedSignatureGuard.type_names(root) }
269
+ names.uniq.map { |name| ::RBS::TypeName.parse("::#{name}") }
270
+ end
271
+
272
+ def quarantined_virtual_count(env, virtual_rbs, declares)
273
+ present = env.buffers.to_set(&:name)
274
+ virtual_rbs.count do |name, content|
275
+ !content.empty? && !present.include?(name) && content.match?(declares) && parseable_rbs?(content)
276
+ end
123
277
  end
124
278
 
125
279
  # rbs ships `stdlib/bigdecimal/` and `stdlib/bigdecimal-math/` as two libraries, so
@@ -1228,6 +1382,7 @@ module Rigor
1228
1382
  # Keyed by `data/vendored_gem_sigs/` directory basename.
1229
1383
  LIBRARY_SUPPLEMENT_VENDORED_DIRS = {
1230
1384
  "cgi" => "cgi",
1385
+ "prime" => "singleton",
1231
1386
  "prism" => "prism"
1232
1387
  }.freeze
1233
1388
  private_constant :LIBRARY_SUPPLEMENT_VENDORED_DIRS
@@ -1258,13 +1413,13 @@ module Rigor
1258
1413
  # overlay is added per-file, not per-directory, because the `LIBRARY_SUPPLEMENT_CORE_OVERLAYS` and
1259
1414
  # `RBS_LINE_CORE_OVERLAYS` files must be gated individually.
1260
1415
  #
1416
+ # A directory {RequiredFeatures} gates loads only when the library list activated it (issue #1700).
1417
+ #
1261
1418
  # @param loaded_library_names — libraries that actually resolved on this loader.
1262
- def add_bundled_signatures(rbs_loader, loaded_library_names)
1419
+ # @param gated_dirs — the {RequiredFeatures}-gated directory basenames this library list activates.
1420
+ def add_bundled_signatures(rbs_loader, loaded_library_names, gated_dirs = [])
1263
1421
  vendored_gem_sig_paths.each do |path|
1264
- next unless path.directory?
1265
- next unless supplement_dependency_loaded?(LIBRARY_SUPPLEMENT_VENDORED_DIRS, path, loaded_library_names)
1266
-
1267
- rbs_loader.add(path: path)
1422
+ rbs_loader.add(path: path) if vendored_dir_loads?(path, loaded_library_names, gated_dirs)
1268
1423
  end
1269
1424
  core_overlay_sig_paths.each do |dir|
1270
1425
  next unless dir.directory?
@@ -1279,6 +1434,31 @@ module Rigor
1279
1434
  end
1280
1435
  end
1281
1436
 
1437
+ # Issue #1700 — the {RequiredFeatures}-gated directories `libraries` activates, less one whose feature
1438
+ # resolved as an RBS library here (`libraries: [prime]` on a host whose `prime` gem ships its `sig/`),
1439
+ # whose own copy is then already loaded. {.build_env_for} then keeps a directory that joined only if the
1440
+ # trial build finds it costs nothing ({.gated_trouble}). Both decisions are made inside the build, against
1441
+ # what the environment holds, so the env-cache producer — which rebuilds through it from the same inputs
1442
+ # — decides the same way.
1443
+ def loadable_gated_dirs(libraries, loaded_libraries)
1444
+ resolved = loaded_libraries.to_set(&:to_s)
1445
+ RequiredFeatures.active_dirs(libraries.map(&:to_s)).reject do |dir|
1446
+ resolved.include?(RequiredFeatures.feature_for_dir(dir))
1447
+ end
1448
+ end
1449
+
1450
+ # @param path — a `data/vendored_gem_sigs/<gem>/` entry.
1451
+ # @return true when the directory exists, its supplemented library (if any) loaded, and — for a
1452
+ # {RequiredFeatures}-gated directory — the library list activated it.
1453
+ def vendored_dir_loads?(path, loaded_library_names, gated_dirs)
1454
+ return false unless path.directory?
1455
+ return false unless supplement_dependency_loaded?(LIBRARY_SUPPLEMENT_VENDORED_DIRS, path,
1456
+ loaded_library_names)
1457
+
1458
+ basename = path.basename.to_s
1459
+ !RequiredFeatures.gated_dir?(basename) || gated_dirs.include?(basename)
1460
+ end
1461
+
1282
1462
  # @param supplements — basename → gating library map.
1283
1463
  # @param path — the vendored directory or overlay file to test.
1284
1464
  # @param loaded_library_names — libraries that actually resolved on this loader.
@@ -1537,6 +1717,32 @@ module Rigor
1537
1717
  @state[:signature_standdowns] ||= self.class.deferred_standdowns(env, @deferred_signature_paths).freeze
1538
1718
  end
1539
1719
 
1720
+ # Issue #1713 — what stood down from each {RequiredFeatures}-gated vendored directory this loader's library
1721
+ # list activated, as {GatedSignaturePlan.standdowns} reports it: `[dir, [[declaration, cause_file, reason],
1722
+ # ...], whole]`. Derived from the final env, so a cache HIT reports what the cold build decided. Empty, without
1723
+ # building the env, when no gated directory is active; empty too for a directory whose feature resolved as
1724
+ # an RBS library, which the build never loads ({.loadable_gated_dirs}).
1725
+ def vendored_standdowns
1726
+ @state[:vendored_standdowns] ||= derive_vendored_standdowns.freeze
1727
+ end
1728
+
1729
+ def derive_vendored_standdowns
1730
+ names = @libraries.map(&:to_s)
1731
+ roots = RequiredFeatures.active_dirs(names).uniq.each_with_object({}) do |dir, acc|
1732
+ acc[dir] = self.class.gated_dir_root(dir) unless gated_feature_resolved?(dir, names)
1733
+ end
1734
+ return [] if roots.empty?
1735
+
1736
+ GatedSignaturePlan.standdowns(env, roots, self.class.project_sig_files(@signature_paths))
1737
+ end
1738
+ private :derive_vendored_standdowns
1739
+
1740
+ def gated_feature_resolved?(dir, library_names)
1741
+ feature = RequiredFeatures.feature_for_dir(dir)
1742
+ library_names.include?(feature) && RBS::EnvironmentLoader.new.has_library?(library: feature, version: nil)
1743
+ end
1744
+ private :gated_feature_resolved?
1745
+
1540
1746
  # Issue #735 — the class / module names whose PRIMARY declaration lives under the project's own
1541
1747
  # `signature_paths:`, top-level prefix stripped ("::Admin::Widget" reads back as "Admin::Widget").
1542
1748
  #
@@ -1807,6 +2013,69 @@ module Rigor
1807
2013
  false
1808
2014
  end
1809
2015
 
2016
+ # Issue #1698 — the modules and classes `name`'s RBS declarations place in its ancestry, read from the
2017
+ # declarations themselves rather than through the definition builder, so a declaration whose definition
2018
+ # fails to build (a missing mixin, a superclass mismatch) still yields what it declares: each declaration's
2019
+ # `include` / `prepend` members and a class's superclass, followed through the other RBS-declared modules
2020
+ # and classes they name, `::`-stripped and without `name` itself. `[names, complete]`: `complete` is false
2021
+ # when a name on the way is not an RBS class or module (an alias, an undeclared mixin), when the walk
2022
+ # passes 64 names, or when the environment is unavailable; `names` is then what was read so far. Interface
2023
+ # includes own no constants and are skipped. Memoised per loader.
2024
+ def declared_ancestry(name)
2025
+ memo = (@state[:declared_ancestry] ||= {})
2026
+ key = name.to_s.delete_prefix("::")
2027
+ memo[key] ||= build_declared_ancestry(key).freeze
2028
+ end
2029
+
2030
+ def build_declared_ancestry(key)
2031
+ environment = env
2032
+ return [[].freeze, false] if environment.nil?
2033
+
2034
+ names = []
2035
+ complete = true
2036
+ pending = [key]
2037
+ until pending.empty?
2038
+ current = pending.shift
2039
+ entry = environment.class_decls[parse_type_name(current)]
2040
+ if entry.nil?
2041
+ complete = false
2042
+ next
2043
+ end
2044
+ declared_parents(entry).each do |parent|
2045
+ next if parent == key || names.include?(parent)
2046
+
2047
+ names << parent
2048
+ pending << parent
2049
+ end
2050
+ next unless names.size > 64
2051
+
2052
+ complete = false
2053
+ break
2054
+ end
2055
+ [names.freeze, complete]
2056
+ rescue ::RBS::BaseError
2057
+ [[].freeze, false]
2058
+ end
2059
+ private :build_declared_ancestry
2060
+
2061
+ def declared_parents(entry)
2062
+ self.class.entry_declarations(entry).flat_map do |decl|
2063
+ parents = decl.respond_to?(:members) ? decl.members.filter_map { |member| mixin_parent(member) } : []
2064
+ superclass = decl.respond_to?(:super_class) ? decl.super_class : nil
2065
+ parents << superclass.name.to_s.delete_prefix("::") if superclass
2066
+ parents
2067
+ end
2068
+ end
2069
+ private :declared_parents
2070
+
2071
+ def mixin_parent(member)
2072
+ return nil unless member.is_a?(::RBS::AST::Members::Include) || member.is_a?(::RBS::AST::Members::Prepend)
2073
+ return nil if member.name.interface?
2074
+
2075
+ member.name.to_s.delete_prefix("::")
2076
+ end
2077
+ private :mixin_parent
2078
+
1810
2079
  # Yields every known class / module / alias name (top-level prefixed) currently loaded into the
1811
2080
  # environment. The cache producer that materialises the known-name set uses this so it never recurses
1812
2081
  # back through {#class_known?}.
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../analysis/path_expansion"
4
+
5
+ module Rigor
6
+ class Environment
7
+ # Issue #1700 — vendored signatures that load only when the project's own source requires the feature. A
8
+ # `data/vendored_gem_sigs/` directory loads for every project, which is safe for a gem that owns its
9
+ # namespace (`PG::Connection`) and unsafe for one that reopens core classes or claims a short top-level name.
10
+ # `prime` does both: it declares `class Prime` and adds `Integer#prime?`, and with it loaded unconditionally a
11
+ # project's own `class Prime` with a zero-argument `prime?` drew a false `call.wrong-arity` against the gem's
12
+ # `(Integer, ?generator)`. Such a directory is listed in {VENDORED_DIRS} and loads only when some project
13
+ # source file requires the feature ({PATTERN}), or the configuration lists it under `libraries:`.
14
+ #
15
+ # The decision travels as a library token ({token}) inside the environment's library list, so every
16
+ # consumer that already keys on that list sees it without plumbing of its own: the RBS environment cache
17
+ # key (`Cache::RbsDescriptor.libraries_entry`), the env-cache producer that rebuilds from
18
+ # `RbsLoader#libraries`, and the run-result key built from the loader's descriptor. The ADR-87 boot-slimming
19
+ # probe reconstructs the list from configuration alone and so never carries a token: for a project that
20
+ # requires a listed feature it misses and the full path serves the run, which forgoes the fast lane and
21
+ # never serves a wrong answer. Whether a token's directory then loads is `RbsLoader`'s decision, made
22
+ # against what the environment actually holds (`RbsLoader.gated_dir_loads?`).
23
+ #
24
+ # A leaf file (no engine requires), for the reason `default_libraries.rb` is one: the incremental snapshot
25
+ # fingerprint reads it without building an environment.
26
+ module RequiredFeatures
27
+ # Required feature => `data/vendored_gem_sigs/` directory basename.
28
+ VENDORED_DIRS = { "prime" => "prime" }.freeze
29
+
30
+ TOKEN_PREFIX = "rigor-vendored:"
31
+
32
+ FEATURE_ALTERNATION = VENDORED_DIRS.keys.map { |feature| Regexp.escape(feature) }.join("|").freeze
33
+ private_constant :FEATURE_ALTERNATION
34
+
35
+ # A `require` call naming a listed feature as a string literal, with or without parentheses: the bare call
36
+ # and `Kernel.require` count, a `require` that follows any other `.` (`obj.require`) or is part of a longer
37
+ # word (`require_relative`) does not, and a computed name never does; nor do `require(` with the name on
38
+ # the next line or `send(:require, "prime")`. A match on a whole-line comment ({#commented?}) does not
39
+ # count, so commenting a require out changes nothing. Otherwise the match is textual and leans toward
40
+ # loading: a `require "prime"` in a trailing comment, a heredoc or an `=begin` block counts. That direction
41
+ # only loads the gem's own signatures, and `RbsLoader` still drops them when they cost the environment
42
+ # anything.
43
+ PATTERN = /(?:(?<![\w.$@])|(?<=Kernel\.))require[ \t]*\(?[ \t]*(["'])(#{FEATURE_ALTERNATION})\1/
44
+
45
+ module_function
46
+
47
+ # The features a run over `configuration` requires: every file under the configured paths, plus
48
+ # `extra_files` (a run's own targets or a probe's file, which may lie outside them). The scan does not
49
+ # depend on which files a run checks, so `rigor check lib/a.rb`, a probe on `lib/a.rb` and the language
50
+ # server all see a `require "prime"` in `lib/b.rb`. Fails soft to no features.
51
+ #
52
+ # @param sources — path => source text, read in place of that path's file (an editor buffer).
53
+ def for_configuration(configuration, extra_files = [], sources: {})
54
+ files = Analysis::PathExpansion.ruby_files(configuration.paths, configuration.exclude_patterns)
55
+ scan(files + Array(extra_files).map(&:to_s), sources: sources)
56
+ rescue StandardError
57
+ []
58
+ end
59
+
60
+ # @param files — source paths (Strings or Pathnames); an unreadable one contributes nothing.
61
+ # @param sources — path => source text, scanned instead of the file; its paths are scanned even when
62
+ # `files` does not list them.
63
+ # @return the sorted, frozen features some file requires.
64
+ def scan(files, sources: {})
65
+ sources = sources.to_h { |path, text| [File.expand_path(path.to_s), text] }
66
+ paths = (Array(files).map { |path| source_key(File.expand_path(path.to_s), sources) } + sources.keys).uniq
67
+ found = Set.new
68
+ paths.each do |path|
69
+ break if found.size == VENDORED_DIRS.size
70
+
71
+ found.merge(sources.key?(path) ? features_of(sources[path]) : memoized_features_in(path))
72
+ end
73
+ prune_memo(paths)
74
+ found.to_a.sort.freeze
75
+ end
76
+
77
+ # @return path (absolute) => the features that file requires, for every file under the configured paths
78
+ # that requires any. A long-lived reader (the language server) keeps this and overlays its open buffers.
79
+ def feature_files(configuration)
80
+ listed = Analysis::PathExpansion.ruby_files(configuration.paths, configuration.exclude_patterns)
81
+ files = listed.map { |path| File.expand_path(path.to_s) }
82
+ result = files.each_with_object({}) do |path, acc|
83
+ features = memoized_features_in(path)
84
+ acc[path] = features unless features.empty?
85
+ end
86
+ prune_memo(files)
87
+ result
88
+ rescue StandardError
89
+ {}
90
+ end
91
+
92
+ # Paths are compared expanded, so a relative `--instead-of=lib/a.rb` replaces the configured
93
+ # `/project/lib/a.rb`; a source whose real path matches (a symlinked directory) replaces it too. The real
94
+ # path is asked only for a file that shares a source's basename, so a scan does not resolve every path.
95
+ def source_key(path, sources)
96
+ return path if sources.empty? || sources.key?(path)
97
+
98
+ candidates = sources.keys.select { |key| File.basename(key) == File.basename(path) }
99
+ return path if candidates.empty?
100
+
101
+ real = real_path(path)
102
+ candidates.find { |key| real_path(key) == real } || path
103
+ end
104
+
105
+ def real_path(path)
106
+ File.realpath(path)
107
+ rescue SystemCallError
108
+ path
109
+ end
110
+
111
+ # One run scans the same files more than once (the incremental snapshot fingerprint, then the environment
112
+ # build; a long-lived language server on every rebuild), so on the main Ractor each file's answer is kept
113
+ # against its stat and the file is re-read only when the stat moved. A non-main Ractor may not touch the
114
+ # module's state and reads the file every time.
115
+ def memoized_features_in(path)
116
+ return features_in(path) unless Ractor.current == Ractor.main
117
+
118
+ stat = File.stat(path)
119
+ stamp = [stat.mtime.to_r, stat.ctime.to_r, stat.size, stat.ino]
120
+ memo = (@memo ||= {})
121
+ cached = memo[path]
122
+ return cached.last if cached && cached.first == stamp
123
+
124
+ features = features_in(path)
125
+ memo[path] = [stamp, features]
126
+ features
127
+ rescue SystemCallError
128
+ []
129
+ end
130
+
131
+ # Keeps the memo to the files the latest scan was asked about, so a long-lived language server holds one
132
+ # entry per project file rather than one per file it ever saw.
133
+ def prune_memo(paths)
134
+ return unless Ractor.current == Ractor.main && @memo && @memo.size > paths.size
135
+
136
+ keep = paths.to_set
137
+ @memo.select! { |path, _| keep.include?(path) }
138
+ end
139
+
140
+ # @return the features one file requires.
141
+ def features_in(path)
142
+ features_of(File.binread(path))
143
+ rescue SystemCallError, IOError
144
+ []
145
+ end
146
+
147
+ # @return the features `source` requires.
148
+ def features_of(source)
149
+ # A plain substring test first: almost no file mentions any listed feature, and it is far cheaper than
150
+ # the regexp on a file that does not.
151
+ return [] unless VENDORED_DIRS.each_key.any? { |feature| source.include?(feature) }
152
+
153
+ bytes = source.b
154
+ named = []
155
+ position = 0
156
+ while (match = PATTERN.match(bytes, position))
157
+ named << match[2] unless commented?(bytes, match.begin(0))
158
+ position = match.end(0)
159
+ end
160
+ # The table's own frozen keys, so the result is shareable across Ractors whatever the source's encoding.
161
+ VENDORED_DIRS.each_key.select { |feature| named.include?(feature) }
162
+ end
163
+
164
+ # Whether the match at `offset` sits on a line whose first non-blank character is `#`: a whole-line comment.
165
+ # Deliberately narrower than "after a `#`": a `#` later on a line may be inside `?#`, `%q(#)`, `%w[a#b]`,
166
+ # a regexp or a string continued from an earlier line, and reading it as a comment would drop a real
167
+ # require — a false `call.undefined-method` on every call into the feature. A trailing comment
168
+ # (`p 1 # require "prime"`) therefore still counts, which only loads the gem's own signatures.
169
+ def commented?(bytes, offset)
170
+ line_start = offset.zero? ? 0 : (bytes.rindex("\n", offset - 1) || -1) + 1
171
+ bytes.byteslice(line_start, offset - line_start).lstrip.start_with?("#")
172
+ end
173
+
174
+ # The library tokens for `features` and for any listed feature the configuration's `libraries:` names.
175
+ def tokens(features, configured_libraries)
176
+ configured = Array(configured_libraries).map(&:to_s).select { |name| VENDORED_DIRS.key?(name) }
177
+ (Array(features) | configured).sort.map { |feature| token(feature) }
178
+ end
179
+
180
+ def token(feature)
181
+ "#{TOKEN_PREFIX}#{feature}"
182
+ end
183
+
184
+ # The feature a library token names, or nil for an ordinary library name.
185
+ def feature_of_token(name)
186
+ name = name.to_s
187
+ name.start_with?(TOKEN_PREFIX) ? name.delete_prefix(TOKEN_PREFIX) : nil
188
+ end
189
+
190
+ # The vendored directory basenames a library list activates.
191
+ def active_dirs(library_names)
192
+ library_names.filter_map { |name| VENDORED_DIRS[feature_of_token(name)] }
193
+ end
194
+
195
+ # Whether `dir_basename` is one of the gated directories.
196
+ def gated_dir?(dir_basename)
197
+ VENDORED_DIRS.value?(dir_basename)
198
+ end
199
+
200
+ # The feature a gated directory loads for.
201
+ def feature_for_dir(dir_basename)
202
+ VENDORED_DIRS.key(dir_basename)
203
+ end
204
+ end
205
+ end
206
+ end
@@ -3,6 +3,7 @@
3
3
  require "digest"
4
4
 
5
5
  require_relative "environment/default_libraries"
6
+ require_relative "environment/required_features"
6
7
  require_relative "environment/class_registry"
7
8
  require_relative "environment/rbs_loader"
8
9
  require_relative "environment/reflection"
@@ -275,7 +276,7 @@ module Rigor
275
276
  bundler_lockfile: nil,
276
277
  rbs_collection_lockfile: nil, rbs_collection_auto_detect: false,
277
278
  synthetic_method_index: nil, project_patched_methods: nil,
278
- source_files: [], locked_gems: nil)
279
+ source_files: [], locked_gems: nil, required_features: nil)
279
280
  resolved_paths = project_signature_roots(signature_paths, root: root)
280
281
  # O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
281
282
  # root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
@@ -314,8 +315,10 @@ module Rigor
314
315
  # and the `data/vendored_gem_sigs/` bundle (`redis`, `nokogiri`, `pg`, …). `skip_gem_names:` passes
315
316
  # both sets so the collection copy doesn't double-declare against rigor's bundled RBS (the
316
317
  # `RBS::DuplicatedDeclarationError` hazard).
317
- merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s)).uniq
318
- skip_gem_names = merged_libraries + RbsLoader.vendored_gem_names
318
+ # Issue #1700 — plus the gated vendored directories this run's source activates ({#feature_tokens}).
319
+ merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s) +
320
+ feature_tokens(required_features, source_files, libraries)).uniq
321
+ skip_gem_names = collection_skip_gem_names(merged_libraries)
319
322
  collection_paths = RbsCollectionDiscovery.discover(
320
323
  lockfile_path: rbs_collection_lockfile,
321
324
  project_root: root,
@@ -400,6 +403,21 @@ module Rigor
400
403
 
401
404
  private
402
405
 
406
+ # Issue #1700 — the `RequiredFeatures` library tokens for the gated vendored directories this run asks for:
407
+ # each feature its source requires and each one the configuration's `libraries:` names. `required_features`
408
+ # is the scan a caller already took; nil scans `source_files`. Whether a directory then loads is decided
409
+ # by `RbsLoader.build_env_for` against the environment it builds.
410
+ def feature_tokens(required_features, source_files, libraries)
411
+ RequiredFeatures.tokens(required_features || RequiredFeatures.scan(source_files), libraries)
412
+ end
413
+
414
+ # The gem names an `rbs collection` copy must not double-declare against: the library list and every
415
+ # vendored directory except a `RequiredFeatures`-gated one. A gated directory stands down for any other
416
+ # copy of its declarations (`GatedSignatureGuard`), so the collection's copy is kept and wins.
417
+ def collection_skip_gem_names(library_names)
418
+ library_names + RbsLoader.vendored_gem_names.reject { |name| RequiredFeatures.gated_dir?(name) }
419
+ end
420
+
403
421
  def default_signature_paths(root)
404
422
  sig = Pathname(root) / DEFAULT_PROJECT_SIG_DIR
405
423
  sig.directory? ? [sig] : []