rigortype 0.4.0 → 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 (237) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  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/08-understanding-errors.md +3 -2
  25. data/docs/handbook/11-sig-gen.md +6 -0
  26. data/docs/handbook/appendix-liskov.md +5 -0
  27. data/docs/install.md +87 -15
  28. data/docs/manual/02-cli-reference.md +2 -2
  29. data/docs/manual/03-configuration.md +6 -1
  30. data/docs/manual/04-diagnostics.md +2 -1
  31. data/docs/manual/06-baseline.md +2 -0
  32. data/docs/manual/07-plugins.md +1 -1
  33. data/docs/manual/08-skills.md +4 -3
  34. data/docs/manual/12-caching.md +43 -4
  35. data/docs/manual/14-rails-quickstart.md +19 -11
  36. data/docs/manual/18-removing-dead-code.md +32 -2
  37. data/docs/manual/plugins/README.md +6 -1
  38. data/docs/manual/plugins/rigor-activerecord.md +21 -0
  39. data/docs/manual/plugins/rigor-alba.md +60 -0
  40. data/docs/manual/plugins/rigor-minitest.md +22 -4
  41. data/docs/manual/plugins/rigor-typelizer.md +71 -0
  42. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
  43. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
  44. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
  45. data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
  47. data/lib/rigor/analysis/check_rules.rb +407 -144
  48. data/lib/rigor/analysis/dependency_recorder.rb +215 -18
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
  52. data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
  53. data/lib/rigor/analysis/erb_template_detector.rb +2 -1
  54. data/lib/rigor/analysis/incremental.rb +29 -9
  55. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
  56. data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
  57. data/lib/rigor/analysis/incremental_session.rb +287 -50
  58. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
  59. data/lib/rigor/analysis/reachability/graph.rb +123 -23
  60. data/lib/rigor/analysis/reachability/scan.rb +290 -36
  61. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
  62. data/lib/rigor/analysis/rule_catalog.rb +43 -0
  63. data/lib/rigor/analysis/run_cache_key.rb +43 -9
  64. data/lib/rigor/analysis/run_stats.rb +2 -2
  65. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
  68. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  69. data/lib/rigor/analysis/runner.rb +371 -49
  70. data/lib/rigor/analysis/shadow_harness.rb +27 -0
  71. data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
  72. data/lib/rigor/analysis/template_unit_paths.rb +6 -4
  73. data/lib/rigor/analysis/template_units.rb +15 -5
  74. data/lib/rigor/analysis/worker_session.rb +11 -7
  75. data/lib/rigor/cache/descriptor.rb +40 -1
  76. data/lib/rigor/cache/file_digest.rb +28 -0
  77. data/lib/rigor/cache/incremental_snapshot.rb +92 -8
  78. data/lib/rigor/cache/store.rb +63 -3
  79. data/lib/rigor/cache/value_digest.rb +325 -0
  80. data/lib/rigor/cli/annotate_command.rb +5 -0
  81. data/lib/rigor/cli/baseline_command.rb +7 -18
  82. data/lib/rigor/cli/check_command.rb +85 -14
  83. data/lib/rigor/cli/doctor_command.rb +1 -1
  84. data/lib/rigor/cli/effects_report.rb +2 -2
  85. data/lib/rigor/cli/trace_command.rb +5 -0
  86. data/lib/rigor/cli/type_of_command.rb +5 -3
  87. data/lib/rigor/cli/unused_command.rb +16 -10
  88. data/lib/rigor/cli.rb +11 -3
  89. data/lib/rigor/config_audit.rb +3 -2
  90. data/lib/rigor/config_path.rb +29 -0
  91. data/lib/rigor/configuration/severity_profile.rb +11 -0
  92. data/lib/rigor/configuration.rb +52 -7
  93. data/lib/rigor/effects/plugin_facts.rb +6 -6
  94. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
  95. data/lib/rigor/environment/gated_signature_guard.rb +43 -0
  96. data/lib/rigor/environment/gated_signature_plan.rb +339 -0
  97. data/lib/rigor/environment/lockfile_resolver.rb +10 -1
  98. data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
  99. data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
  100. data/lib/rigor/environment/rbs_loader.rb +284 -15
  101. data/lib/rigor/environment/required_features.rb +206 -0
  102. data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
  103. data/lib/rigor/environment.rb +34 -73
  104. data/lib/rigor/inference/acceptance.rb +28 -3
  105. data/lib/rigor/inference/block_parameter_binder.rb +29 -13
  106. data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
  107. data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
  108. data/lib/rigor/inference/captured_locals.rb +7 -1
  109. data/lib/rigor/inference/declaration_walk/context.rb +195 -0
  110. data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
  111. data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
  112. data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
  113. data/lib/rigor/inference/declaration_walk.rb +7 -0
  114. data/lib/rigor/inference/def_node_resolver.rb +26 -3
  115. data/lib/rigor/inference/definer_resolution.rb +266 -0
  116. data/lib/rigor/inference/expression_typer.rb +351 -61
  117. data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
  118. data/lib/rigor/inference/global_write_census.rb +7 -4
  119. data/lib/rigor/inference/guard_rebinding.rb +54 -13
  120. data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
  121. data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
  122. data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
  123. data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
  124. data/lib/rigor/inference/in_effect_refinements.rb +256 -0
  125. data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
  126. data/lib/rigor/inference/key_presence_guard.rb +303 -0
  127. data/lib/rigor/inference/macro_block_self_type.rb +73 -14
  128. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  129. data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
  130. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
  131. data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
  132. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
  133. data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
  134. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
  135. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
  136. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
  137. data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
  138. data/lib/rigor/inference/method_dispatcher.rb +44 -11
  139. data/lib/rigor/inference/module_function_state.rb +214 -0
  140. data/lib/rigor/inference/multi_target_binder.rb +21 -16
  141. data/lib/rigor/inference/narrowing.rb +166 -22
  142. data/lib/rigor/inference/object_mixins.rb +180 -0
  143. data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
  144. data/lib/rigor/inference/project_patched_methods.rb +10 -5
  145. data/lib/rigor/inference/project_patched_scanner.rb +71 -3
  146. data/lib/rigor/inference/refine_census.rb +239 -0
  147. data/lib/rigor/inference/refine_self.rb +317 -0
  148. data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
  149. data/lib/rigor/inference/refined_dispatch.rb +272 -0
  150. data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
  151. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
  152. data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
  153. data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
  154. data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
  155. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
  156. data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
  157. data/lib/rigor/inference/scope_indexer.rb +2129 -354
  158. data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
  159. data/lib/rigor/inference/statement_evaluator.rb +149 -32
  160. data/lib/rigor/inference/string_mutation.rb +9 -3
  161. data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
  162. data/lib/rigor/inference/version_guard.rb +19 -10
  163. data/lib/rigor/language_server/project_context.rb +31 -0
  164. data/lib/rigor/language_server/server.rb +18 -0
  165. data/lib/rigor/plugin/base.rb +28 -8
  166. data/lib/rigor/plugin/inflector.rb +1 -1
  167. data/lib/rigor/plugin/io_boundary.rb +124 -7
  168. data/lib/rigor/plugin/isolation.rb +1 -1
  169. data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
  170. data/lib/rigor/plugin/trust_policy.rb +3 -3
  171. data/lib/rigor/project_environment.rb +29 -6
  172. data/lib/rigor/protection/discovery_seed.rb +18 -9
  173. data/lib/rigor/protection/mutation_cache.rb +4 -4
  174. data/lib/rigor/protection/mutator.rb +1 -1
  175. data/lib/rigor/reflection/constant_ancestors.rb +211 -46
  176. data/lib/rigor/reflection/constant_path.rb +33 -7
  177. data/lib/rigor/reflection.rb +41 -10
  178. data/lib/rigor/scope/discovery_index.rb +258 -3
  179. data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
  180. data/lib/rigor/scope/resolution_chain.rb +1032 -0
  181. data/lib/rigor/scope.rb +480 -328
  182. data/lib/rigor/sig_gen/alias_index.rb +1 -1
  183. data/lib/rigor/sig_gen/classification.rb +4 -1
  184. data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
  185. data/lib/rigor/sig_gen/generator.rb +77 -21
  186. data/lib/rigor/sig_gen/method_candidate.rb +2 -2
  187. data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
  188. data/lib/rigor/source/alias_names.rb +52 -0
  189. data/lib/rigor/source/literals.rb +3 -2
  190. data/lib/rigor/source/symbol_name.rb +20 -0
  191. data/lib/rigor/type/combinator.rb +59 -3
  192. data/lib/rigor/type/float_range.rb +4 -1
  193. data/lib/rigor/type/nominal.rb +6 -1
  194. data/lib/rigor/version.rb +1 -1
  195. data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
  196. data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
  197. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
  198. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
  199. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
  200. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
  201. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
  202. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
  203. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
  204. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
  205. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
  206. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
  207. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
  208. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
  209. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
  210. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
  211. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
  212. data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
  213. data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
  214. data/plugins/rigor-alba/sig/alba.rbs +4 -0
  215. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
  216. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
  217. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
  218. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
  219. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
  220. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
  221. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
  222. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
  223. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
  224. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
  225. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
  226. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
  227. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
  228. data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
  229. data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
  230. data/sig/rigor/environment.rbs +2 -0
  231. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  232. data/sig/rigor/scope.rbs +69 -4
  233. data/sig/rigor/source.rbs +4 -0
  234. data/sig/rigor.rbs +3 -1
  235. data/skills/rigor-project-init/SKILL.md +37 -11
  236. data/skills/rigor-project-init/references/02-configure.md +7 -2
  237. metadata +90 -1
@@ -95,12 +95,12 @@ module Rigor
95
95
  # with any of the signature path prefixes (after expansion to absolute paths); `bundled` is the
96
96
  # remainder.
97
97
  def self.partition_classes(class_decl_paths:, signature_paths:)
98
- prefixes = Array(signature_paths).map { |p| File.expand_path(p.to_s) }
98
+ prefixes = Array(signature_paths).map { |p| File.absolute_path(p.to_s) }
99
99
  return [0, class_decl_paths.size] if prefixes.empty?
100
100
 
101
101
  project = 0
102
102
  class_decl_paths.each_value do |path|
103
- expanded = File.expand_path(path)
103
+ expanded = File.absolute_path(path)
104
104
  project += 1 if prefixes.any? { |prefix| expanded.start_with?("#{prefix}/") || expanded == prefix }
105
105
  end
106
106
  [project, class_decl_paths.size - project]
@@ -36,6 +36,8 @@ module Rigor
36
36
  # that stood down against a colliding generic arity (#610), as
37
37
  # `[path, class_name, existing_arity, incoming_arity, existing_file]` tuples. Defaults to none so
38
38
  # a caller that snapshots nothing of the kind need not say so.
39
+ # @param vendored_standdowns_snapshot — issue #1713 — reader returning what each required-feature-gated
40
+ # vendored directory left out (`Environment::RbsLoader#vendored_standdowns`). Defaults to none.
39
41
  # @param env_build_failure_snapshot — reader returning the total RBS env-build failure tuple
40
42
  # (`[error_class, first_error_line, conflicting_buffer_names]`) or nil when the env built.
41
43
  # @param definition_build_failures_snapshot — issue #696 — reader returning the per-class
@@ -55,7 +57,7 @@ module Rigor
55
57
  quarantined_signatures_snapshot:, env_build_failure_snapshot:,
56
58
  definition_build_failures_snapshot:, hkt_scan_failure_snapshot:,
57
59
  conformance_results_snapshot:, signature_standdowns_snapshot: -> { [] },
58
- pooled_run_disclosures: -> { [] })
60
+ vendored_standdowns_snapshot: -> { [] }, pooled_run_disclosures: -> { [] })
59
61
  @configuration = configuration
60
62
  @rbs_extended_reporter = rbs_extended_reporter
61
63
  @boundary_cross_reporter = boundary_cross_reporter
@@ -68,6 +70,7 @@ module Rigor
68
70
  @synthesized_namespaces_snapshot_reader = synthesized_namespaces_snapshot
69
71
  @quarantined_signatures_snapshot_reader = quarantined_signatures_snapshot
70
72
  @signature_standdowns_snapshot_reader = signature_standdowns_snapshot
73
+ @vendored_standdowns_snapshot_reader = vendored_standdowns_snapshot
71
74
  @env_build_failure_snapshot_reader = env_build_failure_snapshot
72
75
  @definition_build_failures_snapshot_reader = definition_build_failures_snapshot
73
76
  @hkt_scan_failure_snapshot_reader = hkt_scan_failure_snapshot
@@ -437,11 +440,23 @@ module Rigor
437
440
  standdowns.map { |entry| build_rbs_plugin_signature_stood_down_diagnostic(entry) }
438
441
  end
439
442
 
440
- # The two `:info` notices that close the `rbs.coverage.*` ladder, in this order: the namespace
441
- # synthesis first, then the stand-down (#610) — the outcome that AVOIDED a definition-build failure
442
- # and so the quietest row on it. One method so the runner's assembly reads them as one slot.
443
+ # Issue #1713 — the other outcome that avoided a definition-build failure: a required-feature-gated
444
+ # vendored directory (`data/vendored_gem_sigs/prime/`) left out what another signature source already
445
+ # declares — a member, a whole type, or, when nothing narrower built, the directory. One `:info` per
446
+ # directory, naming the declarations that stood down and the files that displaced them.
447
+ def rbs_vendored_signature_stood_down_diagnostics
448
+ standdowns = vendored_standdowns_snapshot
449
+ return [] if standdowns.nil? || standdowns.empty?
450
+
451
+ standdowns.map { |entry| build_rbs_vendored_signature_stood_down_diagnostic(entry) }
452
+ end
453
+
454
+ # The `:info` notices that close the `rbs.coverage.*` ladder, in this order: the namespace synthesis
455
+ # first, then the stand-downs (#610, #1713) — the outcomes that AVOIDED a definition-build failure and so
456
+ # the quietest rows on it. One method so the runner's assembly reads them as one slot.
443
457
  def rbs_coverage_notice_diagnostics
444
- rbs_synthesized_namespace_diagnostics + rbs_plugin_signature_stood_down_diagnostics
458
+ rbs_synthesized_namespace_diagnostics + rbs_plugin_signature_stood_down_diagnostics +
459
+ rbs_vendored_signature_stood_down_diagnostics
445
460
  end
446
461
 
447
462
  # Maps the per-run `rigor:v1:conforms-to` scan results into diagnostics (spec: `rbs-extended.md` §
@@ -769,6 +784,69 @@ module Rigor
769
784
  )
770
785
  end
771
786
 
787
+ def build_rbs_vendored_signature_stood_down_diagnostic(entry)
788
+ dir, declarations, whole = entry
789
+ Diagnostic.new(
790
+ path: ".rigor.yml",
791
+ line: 1,
792
+ column: 1,
793
+ message: vendored_standdown_message(dir, declarations, whole),
794
+ severity: :info,
795
+ rule: "rbs.coverage.vendored-signature-stood-down",
796
+ source_family: :builtin
797
+ )
798
+ end
799
+
800
+ def vendored_standdown_message(dir, declarations, whole)
801
+ subject = "Rigor's vendored `#{dir}` signatures (loaded because the project requires `#{dir}` or lists " \
802
+ "it under `libraries:`)"
803
+ return vendored_whole_standdown_message(subject, dir, declarations) if whole
804
+
805
+ vendored_partial_standdown_message(subject, declarations)
806
+ end
807
+
808
+ # One sentence per kind of stand-down present ({Environment::GatedSignaturePlan.standdowns}' reasons).
809
+ VENDORED_STANDDOWN_SENTENCES = {
810
+ "member" => "left out %<count>d method declaration(s) another signature source also makes, since two " \
811
+ "declarations of one method fail the class's definition build, and uses the other " \
812
+ "source's: %<list>s.",
813
+ "type" => "stood %<count>d type(s) down whole, keeping only the types nested in them, because another " \
814
+ "source declares each with a different kind (class or module) or generic arity, or the type " \
815
+ "failed to build beside that source's declaration: %<list>s.",
816
+ "open" => "kept %<count>d type(s) without their superclass, because each failed to build beside the " \
817
+ "other signatures although no other source declares it; calls they no longer answer read " \
818
+ "untyped rather than undefined: %<list>s."
819
+ }.freeze
820
+ private_constant :VENDORED_STANDDOWN_SENTENCES
821
+
822
+ def vendored_partial_standdown_message(subject, declarations)
823
+ sentences = VENDORED_STANDDOWN_SENTENCES.filter_map do |reason, template|
824
+ group = declarations.select { |entry| (entry[2] || "member") == reason }
825
+ next if group.empty?
826
+
827
+ format(template, count: group.size, list: sampled(vendored_standdown_labels(group), 5))
828
+ end
829
+ "#{subject} loaded without some of their declarations. Rigor #{sentences.join(' Rigor ')} The rest of the " \
830
+ "vendored signatures still load. Nothing to fix unless the other declaration is an outdated copy; " \
831
+ "removing it brings the vendored one back."
832
+ end
833
+
834
+ def vendored_whole_standdown_message(subject, dir, declarations)
835
+ labels = vendored_standdown_labels(declarations)
836
+ clashes = labels.empty? ? "" : " Declarations it clashed with: #{sampled(labels, 5)}."
837
+ "#{subject} stood down " \
838
+ "entirely: a type they declare failed to build beside the other signature sources even with the " \
839
+ "clashing declarations left out, so calls into `#{dir}`'s API resolve only against what those " \
840
+ "sources declare.#{clashes}"
841
+ end
842
+
843
+ def vendored_standdown_labels(declarations)
844
+ declarations.map do |declaration, cause_file|
845
+ cause = cause_file.nil? ? "" : " (#{relative_signature_path(cause_file.to_s)})"
846
+ "`#{declaration}`#{cause}"
847
+ end
848
+ end
849
+
772
850
  def displacing_source_phrase(existing_file)
773
851
  return "another loaded signature source" if existing_file.nil?
774
852
 
@@ -965,7 +1043,7 @@ module Rigor
965
1043
 
966
1044
  # Issue #959 — surfaces the {Plugin::IoBoundary} refusal history each loaded plugin accumulated over
967
1045
  # the whole run (prepare AND every per-file call: `#io_boundary` is memoised per plugin instance, so
968
- # one boundary sees both). `TrustPolicy#allow_read?` stays `File.expand_path`-only per ADR-2 — a
1046
+ # one boundary sees both). `TrustPolicy#allow_read?` stays `File.absolute_path`-only per ADR-2 — a
969
1047
  # project rooted under a symlink (macOS' `/tmp` → `/private/tmp` is the common case) can have every
970
1048
  # plugin read fall outside its own read roots with nothing said about it. One `:info` row per plugin
971
1049
  # that hit this, never one per path, naming the count and the first path so the row stays legible on
@@ -1196,6 +1274,10 @@ module Rigor
1196
1274
  @signature_standdowns_snapshot_reader.call
1197
1275
  end
1198
1276
 
1277
+ def vendored_standdowns_snapshot
1278
+ @vendored_standdowns_snapshot_reader.call
1279
+ end
1280
+
1199
1281
  def env_build_failure_snapshot
1200
1282
  @env_build_failure_snapshot_reader.call
1201
1283
  end
@@ -45,7 +45,8 @@ module Rigor
45
45
  snapshots:, plugin_registry:, dependency_source_index:,
46
46
  synthetic_method_index:, project_patched_methods:,
47
47
  analyze_file:, project_scope_seed: -> { {} }, record_dependencies: false,
48
- restored_run_level_rows: nil, template_units: -> { TemplateUnits.empty })
48
+ restored_run_level_rows: nil, template_units: -> { TemplateUnits.empty },
49
+ feature_sources: -> { {} })
49
50
  @configuration = configuration
50
51
  @cache_store = cache_store
51
52
  @explain = explain
@@ -67,6 +68,7 @@ module Rigor
67
68
  @collected_run_disclosures = {}
68
69
  @collect_stats = collect_stats
69
70
  @buffer = buffer
71
+ @feature_sources = feature_sources
70
72
  @environment_override = environment_override
71
73
  @rbs_extended_reporter = rbs_extended_reporter
72
74
  @boundary_cross_reporter = boundary_cross_reporter
@@ -208,7 +210,7 @@ module Rigor
208
210
 
209
211
  env = resolve_sequential_environment(source_files: project_files)
210
212
  end
211
- snapshot_project_signature_state(signature_state_environment(resolve))
213
+ snapshot_project_signature_state(signature_state_environment(resolve, project_files))
212
214
  snapshot_effect_annotation_carrier(
213
215
  annotation_carrier_environment(resolve, in_hand: !env.nil?)&.rbs_loader
214
216
  )
@@ -220,11 +222,21 @@ module Rigor
220
222
  record_hkt_scan_failure(hkt_scan_outcome(resolve.call))
221
223
  end
222
224
 
223
- # Issue #794 — the signature-state snapshot's own gates, asked BEFORE an environment exists. Both
224
- # answers are loader-free (the configuration and the plugin registry), and with neither gate open
225
- # {#snapshot_project_signature_state} writes the same empty state a nil environment gives it.
226
- def signature_state_environment(resolve)
227
- plugin_signature_paths? || project_signature_paths? ? resolve.call : nil
225
+ # Issue #794 — the signature-state snapshot's own gates, asked BEFORE an environment exists. Every
226
+ # answer is loader-free (the configuration, the plugin registry and, issue #1713, the required-feature
227
+ # scan), and with no gate open {#snapshot_project_signature_state} writes the same empty state a nil
228
+ # environment gives it.
229
+ def signature_state_environment(resolve, project_files)
230
+ gated = plugin_signature_paths? || project_signature_paths? || vendored_features_requested?(project_files)
231
+ gated ? resolve.call : nil
232
+ end
233
+
234
+ # Issue #1713 — whether this run activates a required-feature-gated vendored directory, whose stand-down
235
+ # {#snapshot_project_signature_state} reports: the scan every build of this run gates on, or the
236
+ # feature named under `libraries:`.
237
+ def vendored_features_requested?(project_files)
238
+ features = project_files.nil? || project_files.empty? ? [] : required_features_for(project_files)
239
+ !Environment::RequiredFeatures.tokens(features, @configuration.libraries).empty?
228
240
  end
229
241
 
230
242
  # Issue #794 — the same, for the #441 carrier. The carrier is a filter over the loader's
@@ -287,10 +299,10 @@ module Rigor
287
299
  buffers = failure.is_a?(Array) ? failure[3] : nil
288
300
  return false if buffers.nil?
289
301
 
290
- closure_paths = closure.to_set { |path| File.expand_path(path.to_s) }
302
+ closure_paths = closure.to_set { |path| File.absolute_path(path.to_s) }
291
303
  Array(buffers).any? do |name|
292
304
  path = virtual_buffer_path(name)
293
- !path.nil? && closure_paths.include?(File.expand_path(path))
305
+ !path.nil? && closure_paths.include?(File.absolute_path(path))
294
306
  end
295
307
  end
296
308
 
@@ -344,6 +356,10 @@ module Rigor
344
356
  # no such plugin reads nothing off its loader here, as before.
345
357
  @snapshots.signature_standdowns =
346
358
  plugin_signature_paths? ? signature_standdowns_for(environment&.rbs_loader) : []
359
+ # Issue #1713 — NOT behind the `signature_paths:` gate either: what displaces a vendored declaration may
360
+ # be a gem's or a collection's copy. The loader answers without building its env when no gated
361
+ # directory is active.
362
+ @snapshots.vendored_standdowns = vendored_standdowns_for(environment&.rbs_loader)
347
363
  unless project_signature_paths?
348
364
  @snapshots.synthesized_namespaces = []
349
365
  @snapshots.quarantined_signatures = []
@@ -459,7 +475,22 @@ module Rigor
459
475
  **ProjectEnvironment.dependency_discovery_options(@configuration),
460
476
  synthetic_method_index: synthetic_method_index,
461
477
  project_patched_methods: project_patched_methods,
462
- source_files: source_files
478
+ source_files: source_files,
479
+ required_features: required_features_for(source_files)
480
+ )
481
+ end
482
+
483
+ # Issue #1700 — the {Environment::RequiredFeatures} this run's environments gate on: the configured paths
484
+ # plus `source_files`, whichever files the run checks, with an editor buffer or an in-memory source read
485
+ # in place of its file. Taken once per file list and handed to every build (the sequential one, the
486
+ # pool's prewarm and each worker), so they agree and a pool does not re-read the project per worker.
487
+ def required_features_for(source_files)
488
+ key = source_files.map(&:to_s)
489
+ return @required_features if @required_features_key == key
490
+
491
+ @required_features_key = key
492
+ @required_features = Ractor.make_shareable(
493
+ Environment::RequiredFeatures.for_configuration(@configuration, key, sources: @feature_sources.call)
463
494
  )
464
495
  end
465
496
 
@@ -575,9 +606,10 @@ module Rigor
575
606
  # a frozen Array<String>; cheaply shareable. Issue #793 — it IS the full project now
576
607
  # (`source_files`), not the analyzed subset this comment always described.
577
608
  shareable_source_files = source_files.map { |path| path.to_s.dup.freeze }.freeze
609
+ features = required_features_for(source_files)
578
610
 
579
611
  pool = Array.new(@workers) do
580
- Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems) do |configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems| # rubocop:disable Layout/LineLength
612
+ Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems, features) do |configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems, features| # rubocop:disable Layout/LineLength
581
613
  cache_store = cache_root ? Rigor::Cache::Store.new(root: cache_root) : nil
582
614
  session = Rigor::Analysis::WorkerSession.new(
583
615
  configuration: configuration,
@@ -585,7 +617,8 @@ module Rigor
585
617
  plugin_blueprints: blueprints,
586
618
  explain: explain,
587
619
  source_files: shareable_source_files,
588
- locked_gems: locked_gems
620
+ locked_gems: locked_gems,
621
+ required_features: features
589
622
  )
590
623
  main = Ractor.main
591
624
  main.send([:prepare, session.prepare_diagnostics])
@@ -713,7 +746,8 @@ module Rigor
713
746
  project_scope_seed: project_scope_seed,
714
747
  source_files: source_files,
715
748
  record_dependencies: @record_dependencies,
716
- template_units: template_units
749
+ template_units: template_units,
750
+ required_features: required_features_for(source_files)
717
751
  )
718
752
  # Force the full RBS load on the parent so children copy-on-write inherit a warm Environment
719
753
  # rather than each rebuilding it after the fork.
@@ -890,6 +924,7 @@ module Rigor
890
924
  plugin_registry: plugin_registry,
891
925
  source_files: source_files,
892
926
  locked_gems: locked_gems,
927
+ required_features: required_features_for(source_files),
893
928
  **ProjectEnvironment.dependency_discovery_options(@configuration)
894
929
  )
895
930
  warm_env.rbs_loader&.prewarm
@@ -1094,6 +1129,10 @@ module Rigor
1094
1129
  loader.signature_standdowns
1095
1130
  end
1096
1131
 
1132
+ def vendored_standdowns_for(loader)
1133
+ loader.respond_to?(:vendored_standdowns) ? loader.vendored_standdowns : []
1134
+ end
1135
+
1097
1136
  def plugin_registry
1098
1137
  @plugin_registry_reader.call
1099
1138
  end
@@ -23,7 +23,7 @@ module Rigor
23
23
  # ivar surface, preserving the exact assignment order and timing the inline body had. The `pool_mode`
24
24
  # predicate is injected (it keys on `@workers` / `@buffer`, owned by the Runner) so this collaborator
25
25
  # never calls back into it.
26
- class ProjectPrePasses
26
+ class ProjectPrePasses # rubocop:disable Metrics/ClassLength -- the Discovery slots are one declaration per table
27
27
  # Frozen bundle of the EAGER (env-input) project-wide state the pre-passes produce — the slots whose
28
28
  # products feed the RBS environment build. Mirrors the env-input ivars `apply_pre_passes_result`
29
29
  # sets on the {Runner}. The cross-file discovery tables live in the separate {Discovery} bundle
@@ -53,12 +53,13 @@ module Rigor
53
53
  # consumes them and so never pays the double parse (see {#discover} + `Runner#ensure_project_discovery`).
54
54
  # The slot names mirror the discovery half of {Result} exactly.
55
55
  Discovery = Data.define(
56
- :discovered_classes, :discovered_def_nodes, :discovered_def_nestings,
57
- :discovered_singleton_def_nodes, :discovered_def_sources, :discovered_singleton_def_sources,
58
- :discovered_superclasses, :discovered_header_nestings, :discovered_includes, :discovered_prepends,
59
- :discovered_extends, :discovered_class_sources, :constant_values, :constant_sources, :constant_writes,
56
+ :discovered_classes, :discovered_def_nodes, :discovered_def_nestings, :discovered_singleton_def_nodes,
57
+ :discovered_def_sources, :discovered_singleton_def_sources, :discovered_superclasses,
58
+ :discovered_header_nestings, :discovered_includes, :discovered_prepends, :discovered_extends,
59
+ :unpositioned_mixins, :discovered_class_sources, :constant_values, :constant_sources, :constant_writes,
60
60
  :discovered_method_visibilities, :discovered_methods, :discovered_parameter_envelopes, :data_member_layouts,
61
- :struct_member_layouts, :discovered_deferred_ranges, :discovered_refinements, :discovered_global_write_census
61
+ :struct_member_layouts, :discovered_deferred_ranges, :discovered_refinements, :siblings,
62
+ :discovered_global_write_census
62
63
  )
63
64
 
64
65
  # Internal: drives every EAGER project-wide pre-pass — the ones whose products feed the RBS
@@ -172,7 +173,7 @@ module Rigor
172
173
  discovered_prepends: def_index.fetch(:prepends),
173
174
  # Issue #898 — the singleton-side mixin table, kept beside the instance-side one so a class
174
175
  # object's `extend`s reach `Narrowing` from a sibling file the way its `include`s already do.
175
- discovered_extends: def_index.fetch(:extends),
176
+ discovered_extends: def_index.fetch(:extends), unpositioned_mixins: def_index.fetch(:unpositioned_mixins),
176
177
  discovered_class_sources: def_index.fetch(:class_sources),
177
178
  # Issue #644 — the cross-file value-constant publication table and its write attribution.
178
179
  constant_values: def_index.fetch(:constant_values), constant_sources: def_index.fetch(:constant_sources),
@@ -186,7 +187,8 @@ module Rigor
186
187
  # `*_def_shadows_call?` predicates order `sig`-shadowing defs against.
187
188
  discovered_deferred_ranges: def_index.fetch(:deferred_ranges),
188
189
  discovered_refinements: def_index.fetch(:refinements), # Issue #1120
189
- discovered_global_write_census: def_index.fetch(:global_write_census) # Issue #1367
190
+ # Issue #1367 — the census; ADR-119 WD1 — `{sibling name => table}` for `DiscoveryIndex::SIBLINGS`.
191
+ discovered_global_write_census: def_index.fetch(:global_write_census), siblings: def_index.fetch(:siblings)
190
192
  )
191
193
  end
192
194
 
@@ -233,13 +235,19 @@ module Rigor
233
235
  # {Rigor::Plugin::Registry} WITHOUT the synthetic-method / dependency-source / pre-eval scanners. The
234
236
  # incremental fact-surface fingerprint probe ({Analysis::PluginFactFingerprint}) uses this to read the
235
237
  # ADR-9 fact store + drive the ADR-60 producers without paying for the full pre-pass or building the RBS
236
- # environment. `prepare` runs unconditionally here — the probe is always sequential (its `pool_mode?`
238
+ # environment. `prepare` runs whatever the pool mode — the probe is always sequential (its `pool_mode?`
237
239
  # reader returns false), so the pool-mode skip in {#run} does not apply. Prepare diagnostics are
238
240
  # discarded: a plugin that raises in `#prepare` publishes no facts, so its surface is simply absent from
239
241
  # the fingerprint, exactly as it would be absent from analysis.
240
- def prepared_registry
242
+ #
243
+ # Issue #1536 — `prepare: false` loads the plugins without running `#prepare`, for a caller that reads
244
+ # only what a manifest declares: the incremental session reads each plugin's `source_rbs_synthesizer`
245
+ # before its recheck runner exists. A synthesizer's output is a function of the file, the plugin's
246
+ # config and the engine — the key its `Cache::Store` entry already rests on — so `#prepare` has
247
+ # nothing to add to it, and skipping it keeps that read off every plugin's prepare-time scans.
248
+ def prepared_registry(prepare: true)
241
249
  registry = load_plugins
242
- plugin_prepare_diagnostics(registry) unless registry.empty?
250
+ plugin_prepare_diagnostics(registry) if prepare && !registry.empty?
243
251
  registry
244
252
  end
245
253
 
@@ -288,12 +296,12 @@ module Rigor
288
296
  def build_trust_policy
289
297
  trusted_gems = @configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
290
298
  roots = [Dir.pwd]
291
- Array(@configuration.signature_paths).each { |sp| roots << File.expand_path(sp) }
299
+ Array(@configuration.signature_paths).each { |sp| roots << File.absolute_path(sp) }
292
300
  trusted_gems.each do |gem_name|
293
301
  path = trusted_gem_root(gem_name)
294
302
  roots << path if path
295
303
  end
296
- @configuration.plugins_io_allowed_paths.each { |p| roots << File.expand_path(p) }
304
+ @configuration.plugins_io_allowed_paths.each { |p| roots << File.absolute_path(p) }
297
305
 
298
306
  Plugin::TrustPolicy.new(
299
307
  trusted_gems: trusted_gems,
@@ -16,7 +16,8 @@ module Rigor
16
16
  # back-reference cycle.
17
17
  class RunSnapshots
18
18
  attr_accessor :class_decl_paths, :signature_paths,
19
- :synthesized_namespaces, :quarantined_signatures, :signature_standdowns, :conformance_results,
19
+ :synthesized_namespaces, :quarantined_signatures, :signature_standdowns, :vendored_standdowns,
20
+ :conformance_results,
20
21
  :env_build_failure, :definition_build_failures, :hkt_scan_failure,
21
22
  :effect_annotation_carrier
22
23
 
@@ -32,12 +33,15 @@ module Rigor
32
33
  # `signature_standdowns` (#610) is the quarantine slot's twin for the plugin-contributed files that
33
34
  # stood down against a colliding generic arity: a list, assigned once from the loader as
34
35
  # `quarantined_signatures` is, and derived from the final env so a cache HIT carries it too.
36
+ # `vendored_standdowns` (#1713) is the same shape of slot for what a required-feature-gated vendored
37
+ # directory left out because another signature source declares it.
35
38
  def initialize
36
39
  @class_decl_paths = {}.freeze
37
40
  @signature_paths = [].freeze
38
41
  @synthesized_namespaces = [].freeze
39
42
  @quarantined_signatures = [].freeze
40
43
  @signature_standdowns = [].freeze
44
+ @vendored_standdowns = [].freeze
41
45
  @conformance_results = [].freeze
42
46
  @env_build_failure = nil
43
47
  @definition_build_failures = [].freeze
@@ -53,6 +57,7 @@ module Rigor
53
57
  @synthesized_namespaces = []
54
58
  @quarantined_signatures = []
55
59
  @signature_standdowns = []
60
+ @vendored_standdowns = []
56
61
  @conformance_results = []
57
62
  @env_build_failure = nil
58
63
  @definition_build_failures = []