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
@@ -22,7 +22,12 @@ module Rigor
22
22
  # whose kind (class or module) contradicts another source's, stands down on its own, where adding it would
23
23
  # have raised `RBS::DuplicatedDeclarationError` and dropped the plugin's whole file;
24
24
  # - a class header's superclass clause another source already gives the class one stands down (#2439), where two
25
- # different clauses would have raised `RBS::SuperclassMismatchError`; the plugin's declaration reopens the class.
25
+ # different clauses would have raised `RBS::SuperclassMismatchError`; the plugin's declaration reopens the class;
26
+ # - an `include`, `extend` or `prepend` of a module this file declares but that stood down whole, or that another
27
+ # source declares as a class, stands down too (#2974), where keeping it would have raised `NoMixinFoundError` or
28
+ # `MixinClassError` for the class that mixes it in (`Integer`, for the overlay's `ActiveSupport::NumericWithFormat`);
29
+ # - an instance, class-instance or class variable another source declares on the type stands down (#2978), where
30
+ # two declarations of it would have raised `InstanceVariableDuplicationError` or its twins for the class.
26
31
  #
27
32
  # A declaration with nothing taken comes back as the same object, so a file with no collision loads as it
28
33
  # stands. {.standdown_rows} reads what stood down back off the built environment, so a warm run off the env
@@ -33,27 +38,30 @@ module Rigor
33
38
  # @param own — the buffer names that are the project's own sources: its `signature_paths:` files (not the
34
39
  # collection or gem directories also on that list) and its inline RBS.
35
40
  def plan(decls, env, own, namespace = nil)
36
- planned = decls.filter_map { |decl| plan_declaration(decl, env, own, namespace) }
41
+ mixins = MixinGate.new(env, decls)
42
+ planned = decls.filter_map { |decl| plan_declaration(decl, env, own, namespace, mixins) }
37
43
  same?(planned, decls) ? decls : planned
38
44
  end
39
45
 
40
- def plan_declaration(decl, env, own, namespace)
46
+ def plan_declaration(decl, env, own, namespace, mixins)
41
47
  name = GatedSignaturePlan.qualify(namespace, declared_name(decl))
42
48
  if GatedSignaturePlan.type_declaration?(decl)
43
49
  return nil if kind_clash?(env, decl, name)
44
50
 
45
- return plan_type(decl, env, own, name)
51
+ return plan_type(decl, env, own, name, mixins)
46
52
  end
47
53
  declared_elsewhere(env, decl, name) ? nil : decl
48
54
  end
49
55
 
50
- def plan_type(decl, env, own, name)
56
+ def plan_type(decl, env, own, name, mixins)
51
57
  others = GatedSignaturePlan.other_declarations(env, name)
52
58
  taken = GatedSignaturePlan.declared_keys(others)
59
+ variables = variable_keys(others)
53
60
  external = others.reject { |other| own.include?(buffer_of(other)) }
54
61
  defined = GatedSignaturePlan.declared_keys(external) | definition_keys(external)
55
62
  members = decl.members.filter_map do |member|
56
- next plan_declaration(member, env, own, name) if declaration?(member)
63
+ next plan_declaration(member, env, own, name, mixins) if declaration?(member)
64
+ next nil if mixins.drop?(member, name) || variables.include?(variable_key(member))
57
65
  next member if taken.empty?
58
66
 
59
67
  composed(member, defined) || GatedSignaturePlan.untaken(member, taken)
@@ -64,6 +72,40 @@ module Rigor
64
72
  GatedSignaturePlan.rebuild(decl, members: members, drop_super_class: drop)
65
73
  end
66
74
 
75
+ # Issue #2978 — an instance, class-instance or class variable another source declares on the type stands down:
76
+ # RBS raises `InstanceVariableDuplicationError` (or its class-instance and class-variable twins) for two
77
+ # declarations of one, and the class reads `Dynamic[top]`. Keyed by name and kind, not type: the other source's
78
+ # declaration binds either way.
79
+ VARIABLE_MEMBERS = {
80
+ ::RBS::AST::Members::InstanceVariable => :instance_variable,
81
+ ::RBS::AST::Members::ClassInstanceVariable => :class_instance_variable,
82
+ ::RBS::AST::Members::ClassVariable => :class_variable
83
+ }.freeze
84
+
85
+ # `[name, kind]` for a variable member, else nil.
86
+ def variable_key(member)
87
+ kind = VARIABLE_MEMBERS[member.class]
88
+ kind && [member.name, kind]
89
+ end
90
+
91
+ # The variable keys `decls` declare, an attribute's implied instance variable included (`attr_reader x: String`
92
+ # declares `@x` unless written `attr_reader x(): String`).
93
+ def variable_keys(decls)
94
+ decls.each_with_object(Set.new) do |decl, keys|
95
+ decl.members.each do |member|
96
+ key = variable_key(member) || attribute_variable_key(member)
97
+ keys << key if key
98
+ end
99
+ end
100
+ end
101
+
102
+ def attribute_variable_key(member)
103
+ return nil unless member.is_a?(::RBS::AST::Members::Attribute) && member.ivar_name != false
104
+
105
+ [member.ivar_name || :"@#{member.name}",
106
+ member.kind == :singleton ? :class_instance_variable : :instance_variable]
107
+ end
108
+
67
109
  # Issue #2439 — true when `decl` is a class with a superclass clause and another source (`others`) already
68
110
  # gives the class one. Two different clauses raise `RBS::SuperclassMismatchError` and the class and every
69
111
  # descendant read `Dynamic[top]`, so the plugin's header stands down and reopens the class without one. The
@@ -140,6 +182,62 @@ module Rigor
140
182
  RbsLoader.declaration_buffer_name(decl)
141
183
  end
142
184
 
185
+ # Issue #2974 — which of a deferred file's mixins name a module that will not be there as a module: one the file
186
+ # itself declares, inside a type that stood down whole on a kind clash (the project's `class ActiveSupport` takes
187
+ # the overlay's `module ActiveSupport` and the `NumericWithFormat` in it), or one another source declares as a
188
+ # class. A mixin of a module the file does not declare, which some other source may supply, is left alone.
189
+ class MixinGate
190
+ MIXINS = [::RBS::AST::Members::Include, ::RBS::AST::Members::Extend, ::RBS::AST::Members::Prepend].freeze
191
+
192
+ def initialize(env, decls)
193
+ @env = env
194
+ @declared = Set.new
195
+ @dropped = Set.new
196
+ decls.each { |decl| collect(decl, nil, false) }
197
+ end
198
+
199
+ # `namespace` is the absolute name, without the leading `::`, of the type `member` is written in.
200
+ def drop?(member, namespace)
201
+ return false unless MIXINS.any? { |klass| member.is_a?(klass) } && member.name.class?
202
+
203
+ target = resolve(member.name, namespace)
204
+ return false if target.nil?
205
+
206
+ first = GatedSignaturePlan.other_declarations(@env, target).first
207
+ return first.is_a?(::RBS::AST::Declarations::Class) unless first.nil?
208
+
209
+ @dropped.include?(target)
210
+ end
211
+
212
+ private
213
+
214
+ def collect(decl, namespace, inside_dropped)
215
+ return unless GatedSignaturePlan.type_declaration?(decl)
216
+
217
+ name = GatedSignaturePlan.qualify(namespace, decl.name)
218
+ dropped = inside_dropped || DeferredSignaturePlan.kind_clash?(@env, decl, name)
219
+ @declared << name
220
+ @dropped << name if dropped
221
+ decl.members.each { |member| collect(member, name, dropped) }
222
+ end
223
+
224
+ # The first lexical candidate for `type_name` written inside `namespace` that this file or the env declares,
225
+ # as RBS resolves a relative name outward from the innermost scope.
226
+ def resolve(type_name, namespace)
227
+ candidates(type_name, namespace).find do |name|
228
+ @declared.include?(name) || !GatedSignaturePlan.other_declarations(@env, name).empty?
229
+ end
230
+ end
231
+
232
+ def candidates(type_name, namespace)
233
+ text = type_name.to_s
234
+ return [text.delete_prefix("::")] if type_name.absolute?
235
+
236
+ scopes = namespace.to_s.split("::")
237
+ scopes.size.downto(0).map { |depth| [*scopes.first(depth), text].join("::") }
238
+ end
239
+ end
240
+
143
241
  # One {.standdown_rows} walk over a deferred file: the built env, the file's buffer name, the rows so far, and
144
242
  # the env's type-name resolver, built on first use (only a dropped superclass clause needs it).
145
243
  class Walk
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../typing_state"
4
+
3
5
  module Rigor
4
6
  class Environment
5
7
  # ADR-20 slice 2e — mutable single-slot memoization container for the per-Environment HKT registry. Held
@@ -10,6 +12,10 @@ module Rigor
10
12
  # prevent cross-thread races. If a future caller introduces a multi-threaded reader path against a
11
13
  # shared Environment, the synchronisation belongs at that caller's seam, not here.
12
14
  class HktRegistryHolder
15
+ TypingState.memo_field(self, :@value, :@loaded,
16
+ reason: "the one value its owner's block builds from the environment's fixed inputs, " \
17
+ "kept once built")
18
+
13
19
  def initialize
14
20
  @loaded = false
15
21
  @value = nil
@@ -19,12 +19,16 @@ module Rigor
19
19
  # access. Failure modes (missing lockfile, malformed YAML, missing collection directory) silently
20
20
  # degrade to an empty list.
21
21
  module RbsCollectionDiscovery
22
- # `stdlib`-typed entries in the lockfile are loaded into the RBS environment by the standard library
23
- # mechanism (rigor's `Environment::DEFAULT_LIBRARIES` already covers this surface). Including them as
24
- # `signature_paths:` entries would risk `RBS::DuplicatedDeclarationError` (the same hazard O7's
25
- # failure-memo handles). The other documented source types — `git` (the gem_rbs_collection repo),
26
- # `rubygems` (sigs lifted from a gem's bundled `sig/`), and `local` (a user-managed RBS dir) — all
27
- # produce a directory under the collection root and are admitted.
22
+ # `stdlib`-typed entries (`source: { type: stdlib }`) name a library rbs ships under its own `stdlib/`, and
23
+ # produce no directory under the collection root, so {.discover} never returns one. Issue #2946 — they are
24
+ # loaded all the same, as the `rbs` CLI's `EnvironmentLoader#add_collection` loads them: as RBS LIBRARIES.
25
+ # {.stdlib_library_names} lists them, and `Environment::BaseLibraries.resolve` appends them to the library list,
26
+ # which is de-duplicated, so an entry `DEFAULT_LIBRARIES` already names is not loaded twice. A collection
27
+ # gem that subclasses one (actionview's `NodeParser < ::Ripper`) then builds. An unknown name is dropped by
28
+ # the loader's `has_library?` filter, as a `libraries:` name is, and `ConfigAudit` reports it. The other
29
+ # documented source types — `git` (the gem_rbs_collection repo), `rubygems` (sigs lifted from a gem's
30
+ # bundled `sig/`), and `local` (a user-managed RBS dir) — all produce a directory under the collection root
31
+ # and are admitted by {.discover}.
28
32
  #
29
33
  # The `source.type` filter alone is NOT sufficient: gems that were extracted from Ruby's stdlib into
30
34
  # standalone default gems (e.g. `cgi`, `logger`, `base64`, `csv`, `bigdecimal`) are published in
@@ -77,6 +81,110 @@ module Rigor
77
81
  Rigor::ConfigPath.absolute(lockfile_path.to_s, project_root)
78
82
  end
79
83
 
84
+ # Issue #2946 — the `name` of every `source: { type: stdlib }` entry in the lockfile, in lockfile order and
85
+ # de-duplicated, whether or not rbs ships that library: the caller loads them as RBS libraries, and the
86
+ # loader's `has_library?` filter drops an unknown one. Unlike {.discover}, the collection directory need not
87
+ # exist, since a stdlib entry installs nothing there. Returns `[]` when no lockfile resolves or its YAML is
88
+ # unreadable. A pure YAML read, so the ADR-87 boot-slimming probe can rebuild the library list from it.
89
+ def self.stdlib_library_names(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
90
+ resolved = resolve_lockfile_path(lockfile_path: lockfile_path, project_root: project_root,
91
+ auto_detect: auto_detect)
92
+ return [] if resolved.nil?
93
+
94
+ data = read_lockfile_yaml(resolved)
95
+ return [] if data.nil?
96
+
97
+ Array(data["gems"]).filter_map do |entry|
98
+ next unless entry.is_a?(Hash) && SKIPPED_SOURCE_TYPES.include?(source_type(entry))
99
+
100
+ name = entry["name"]
101
+ name.to_s if name.is_a?(String) && !name.empty?
102
+ end.uniq
103
+ end
104
+
105
+ # Issue #2971 — the `name` of every lockfile entry that is NOT a `stdlib` source and whose
106
+ # `<root>/<name>/<version>/` directory exists: the gems whose signatures come from the collection ({.discover}).
107
+ # The `libraries:` audit uses it so a name the collection provides is not reported as an unknown RBS library.
108
+ # A pure file read, like {.stdlib_library_names}.
109
+ def self.collection_gem_names(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
110
+ resolved = resolve_lockfile_path(lockfile_path: lockfile_path, project_root: project_root,
111
+ auto_detect: auto_detect)
112
+ return [] if resolved.nil?
113
+
114
+ data = read_lockfile_yaml(resolved)
115
+ return [] if data.nil?
116
+
117
+ collection_root = resolve_collection_root(resolved, data)
118
+ gem_paths_from(collection_root, data, Set.new).map { |dir| dir.parent.basename.to_s }.uniq
119
+ end
120
+
121
+ # Issue #3030 — whether `file`, found under the collection gem directory `root`, sits below a `_`-prefixed
122
+ # directory (`_test/`, `_scripts/`). Such a file is not part of the gem's signatures: rbs's
123
+ # `EnvironmentLoader` loads a collection gem as a library, and `RBS::FileFinder.each_file(skip_hidden: true)`
124
+ # drops every file with a `_`-prefixed directory between it and the library root. A `git` source never
125
+ # copies them into the collection (`Sources::Git#cp_r` prunes them), but a `local` source symlinks the whole
126
+ # `<name>/<version>/` directory, so gem_rbs_collection's own `bin/test` setup brings each gem's `_test/`
127
+ # fixtures along. Only the directories count, as in rbs: a `_`-prefixed file name is loaded.
128
+ def self.hidden_file?(root, file)
129
+ relative = Pathname(file.to_s).relative_path_from(Pathname(root.to_s))
130
+ relative.ascend.drop(1).any? { |part| part.basename.to_s.start_with?("_") }
131
+ end
132
+
133
+ # Issue #3030 — `collection_paths` ({.discover}) without each gem directory a `signature_paths:` root already
134
+ # loads a copy of. A root is a copy of the entry `<collection>/<name>/<version>` when its real path is the
135
+ # entry's real path (a `local` source symlinks the working tree in), or when it is a gem_rbs_collection working
136
+ # tree for the same gem and version, `.../gems/<name>/<version>` (a `git` source installs a copy of it). That is
137
+ # what an author editing the tree points `signature_paths:` at from its `_test/` directory while the lockfile
138
+ # installs the same gem, and loading both declares every member twice. The `signature_paths:` copy is kept, as
139
+ # it is the one being edited. Any other root, such as a partial override `sig/overrides/foo/1.0`, leaves the
140
+ # collection copy loaded. Real paths are read so `..` and a symlinked spelling answer the same; a root that
141
+ # does not exist matches nothing.
142
+ def self.without_signature_path_copies(collection_paths, signature_roots)
143
+ reals = signature_roots.filter_map { |root| real_path(root) }
144
+ return collection_paths if reals.empty?
145
+
146
+ trees = reals.filter_map do |real|
147
+ real = Pathname(real)
148
+ [real.parent.basename.to_s, real.basename.to_s] if real.parent.parent.basename.to_s == "gems"
149
+ end.to_set
150
+ reals = reals.to_set
151
+ collection_paths.reject do |path|
152
+ path = Pathname(path.to_s)
153
+ trees.include?([path.parent.basename.to_s, path.basename.to_s]) || reals.include?(real_path(path))
154
+ end
155
+ end
156
+
157
+ def self.real_path(path)
158
+ File.realpath(path.to_s)
159
+ rescue SystemCallError
160
+ nil
161
+ end
162
+ private_class_method :real_path
163
+
164
+ # Issue #3030 — why a run with no collection lockfile looks like it was meant to have one: `:collection_test_dir`
165
+ # when `project_root` is a gem_rbs_collection test directory (`gems/<name>/<version>/_test/`, whose lockfile
166
+ # gem_rbs_collection's `bin/test` setup writes), `:collection_config` when it holds an `rbs_collection.yaml`
167
+ # (`rbs collection install` has not run), and nil otherwise, or when a lockfile resolves or the lookup is off.
168
+ # Rigor resolves no `manifest.yaml` or gemspec dependency itself, so without the lockfile the dependency RBS
169
+ # is absent. Two `stat`s and no read.
170
+ def self.missing_lockfile_hint(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
171
+ return nil if lockfile_path.nil? && !auto_detect
172
+ return nil if resolve_lockfile_path(lockfile_path: lockfile_path, project_root: project_root,
173
+ auto_detect: auto_detect)
174
+
175
+ root = Pathname(File.expand_path(project_root.to_s))
176
+ return :collection_test_dir if collection_test_dir?(root)
177
+ return :collection_config if root.join("rbs_collection.yaml").file?
178
+
179
+ nil
180
+ end
181
+
182
+ # `gems/<name>/<version>/_test/`, the layout of a gem_rbs_collection working tree.
183
+ def self.collection_test_dir?(root)
184
+ root.basename.to_s == "_test" && root.parent.parent.parent.basename.to_s == "gems"
185
+ end
186
+ private_class_method :collection_test_dir?
187
+
80
188
  # Returns the resolved lockfile path (`Pathname`) or `nil` when neither explicit nor auto-detect
81
189
  # produces one. Public so the stats banner can surface what rigor found.
82
190
  def self.resolve_lockfile_path(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
@@ -110,12 +218,19 @@ module Rigor
110
218
  end
111
219
  private_class_method :resolve_collection_root
112
220
 
221
+ # An entry's `source.type`, or "" when `source:` is absent or not a mapping (`source: stdlib`): a malformed
222
+ # entry fails soft (ADR-5) instead of raising out of the run.
223
+ def self.source_type(entry)
224
+ source = entry["source"]
225
+ source.is_a?(Hash) ? source["type"].to_s : ""
226
+ end
227
+ private_class_method :source_type
228
+
113
229
  def self.gem_paths_from(collection_root, data, skip_gem_names)
114
230
  Array(data["gems"]).filter_map do |entry|
115
231
  next unless entry.is_a?(Hash)
116
232
 
117
- source_type = entry.dig("source", "type").to_s
118
- next if SKIPPED_SOURCE_TYPES.include?(source_type)
233
+ next if SKIPPED_SOURCE_TYPES.include?(source_type(entry))
119
234
 
120
235
  name = entry["name"]
121
236
  version = entry["version"]
@@ -5,8 +5,9 @@ module Rigor
5
5
  # Open item O4 Layer 3 slice 3 — graceful-degradation coverage report.
6
6
  #
7
7
  # When the user has a `Gemfile.lock` (via slice 1) and rigor has resolved its target-project RBS sources
8
- # (DEFAULT_LIBRARIES, `data/vendored_gem_sigs/`, slice-1 bundle-shipped `sig/`, slice-2
9
- # `rbs_collection.lock.yaml` paths), this module classifies each locked gem by RBS provenance and
8
+ # (the resolved library list — `Environment::BaseLibraries.resolve`, `data/vendored_gem_sigs/`,
9
+ # slice-1 bundle-shipped `sig/`, slice-2 `rbs_collection.lock.yaml` paths), this module classifies each
10
+ # locked gem by RBS provenance and
10
11
  # surfaces the "no RBS available" set so the run-start diagnostic in {Rigor::Analysis::Runner} can
11
12
  # suggest `rbs collection install` or `dependencies.source_inference:` for the uncovered gems.
12
13
  #
@@ -37,9 +38,9 @@ module Rigor
37
38
 
38
39
  # @param locked_gems — The lockfile-resolved gem set. Empty
39
40
  # hash → no coverage analysis to do.
40
- # @param default_libraries — gem names rigor auto-loads through
41
- # `RBS::EnvironmentLoader#add(library:)`. Pass `Rigor::Environment::DEFAULT_LIBRARIES` from callers
42
- # running in a project context.
41
+ # @param default_libraries — the resolved list of names rigor loads as RBS libraries
42
+ # (`Environment::BaseLibraries.resolve`: `DEFAULT_LIBRARIES` + `libraries:` + the collection
43
+ # lockfile's stdlib entries). Pass it from callers running in a project context.
43
44
  # @param bundle_sig_paths — the discovered `<bundle>/.../gems/<name>-<ver>/sig`
44
45
  # paths from {BundleSigDiscovery.discover}.
45
46
  # @param rbs_collection_paths — the discovered `<collection>/<name>/<version>/`
@@ -48,7 +49,7 @@ module Rigor
48
49
  def self.classify(locked_gems:, default_libraries:,
49
50
  bundle_sig_paths:, rbs_collection_paths:)
50
51
  default_set = default_libraries.to_set
51
- bundle_names = extract_gem_names_from_bundle_paths(bundle_sig_paths)
52
+ bundle_names = extract_gem_names_from_bundle_paths(bundle_sig_paths, locked_gems)
52
53
  collection_names = extract_gem_names_from_collection_paths(rbs_collection_paths)
53
54
 
54
55
  locked_gems.each_value.map do |locked|
@@ -73,11 +74,11 @@ module Rigor
73
74
  coverage_rows.select { |row| row.source == :missing }
74
75
  end
75
76
 
76
- def self.extract_gem_names_from_bundle_paths(paths)
77
- paths.each_with_object(Set.new) do |path, set|
78
- pathname = path.is_a?(Pathname) ? path : Pathname.new(path)
79
- set << BundleSigDiscovery.gem_name_from_sig_path(pathname)
80
- end
77
+ # Issue #2978 — each path's gem name as the locked entry it matches ({BundleSigDiscovery.locked_names_of}), not
78
+ # a parse of its directory name, which reads `foo-2fa-1.0` as `foo` and reported `foo-2fa` as missing RBS.
79
+ def self.extract_gem_names_from_bundle_paths(paths, locked_gems)
80
+ pathnames = paths.map { |path| path.is_a?(Pathname) ? path : Pathname.new(path) }
81
+ BundleSigDiscovery.locked_names_of(pathnames, locked_gems).to_set
81
82
  end
82
83
  private_class_method :extract_gem_names_from_bundle_paths
83
84
 
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../typing_state"
4
+
3
5
  module Rigor
4
6
  class Environment
5
7
  # Small hierarchy oracle backed by RBS instance definitions.
6
8
  class RbsHierarchy
9
+ TypingState.memo_field(self, :@ancestor_names_cache, :@class_ordering_cache,
10
+ reason: "keyed on the class names; answered from the loader's RBS environment, fixed " \
11
+ "for the loader's lifetime")
12
+
7
13
  def initialize(loader)
8
14
  @loader = loader
9
15
  @ancestor_names_cache = {}