rigortype 0.4.4 → 0.4.5

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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/docs/handbook/11-sig-gen.md +90 -17
  4. data/docs/manual/02-cli-reference.md +39 -9
  5. data/docs/manual/03-configuration.md +10 -8
  6. data/docs/manual/04-diagnostics.md +16 -5
  7. data/docs/manual/05-inspecting-types.md +2 -2
  8. data/docs/manual/06-baseline.md +1 -1
  9. data/docs/manual/07-plugins.md +2 -1
  10. data/docs/manual/08-skills.md +7 -0
  11. data/docs/manual/10-mcp-server.md +8 -1
  12. data/docs/manual/18-removing-dead-code.md +18 -7
  13. data/docs/manual/plugins/rigor-rspec.md +101 -1
  14. data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
  15. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +111 -7
  16. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +190 -0
  17. data/lib/rigor/analysis/check_rules/rule_ids.rb +3 -1
  18. data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
  19. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +13 -4
  20. data/lib/rigor/analysis/check_rules.rb +228 -15
  21. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  22. data/lib/rigor/analysis/incremental.rb +49 -0
  23. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
  24. data/lib/rigor/analysis/incremental_session.rb +166 -24
  25. data/lib/rigor/analysis/path_expansion.rb +3 -1
  26. data/lib/rigor/analysis/reachability/graph.rb +90 -13
  27. data/lib/rigor/analysis/reachability/scan.rb +279 -39
  28. data/lib/rigor/analysis/rule_catalog.rb +17 -4
  29. data/lib/rigor/analysis/runner/pool_coordinator.rb +69 -16
  30. data/lib/rigor/analysis/runner.rb +69 -65
  31. data/lib/rigor/analysis/severity_stamp.rb +11 -0
  32. data/lib/rigor/analysis/worker_session.rb +12 -1
  33. data/lib/rigor/cache/descriptor.rb +40 -7
  34. data/lib/rigor/cache/engine_source.rb +3 -2
  35. data/lib/rigor/cache/incremental_snapshot.rb +26 -8
  36. data/lib/rigor/cache/rbs_descriptor.rb +2 -1
  37. data/lib/rigor/cache/store.rb +4 -3
  38. data/lib/rigor/cli/command.rb +3 -1
  39. data/lib/rigor/cli/docs_command.rb +3 -2
  40. data/lib/rigor/cli/doctor_command.rb +1 -1
  41. data/lib/rigor/cli/explain_command.rb +3 -2
  42. data/lib/rigor/cli/plugin_command.rb +2 -1
  43. data/lib/rigor/cli/plugins_command.rb +2 -1
  44. data/lib/rigor/cli/sig_gen_command.rb +32 -11
  45. data/lib/rigor/cli/skill_command.rb +2 -1
  46. data/lib/rigor/cli/skill_describe.rb +3 -2
  47. data/lib/rigor/cli/unused_command.rb +10 -7
  48. data/lib/rigor/cli.rb +3 -2
  49. data/lib/rigor/config_audit.rb +60 -15
  50. data/lib/rigor/configuration/severity_profile.rb +20 -2
  51. data/lib/rigor/configuration.rb +29 -3
  52. data/lib/rigor/effects/signature_sources.rb +3 -1
  53. data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
  54. data/lib/rigor/environment/gated_signature_guard.rb +2 -1
  55. data/lib/rigor/environment/gated_signature_plan.rb +2 -1
  56. data/lib/rigor/environment/installed_gem_set.rb +2 -1
  57. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
  58. data/lib/rigor/environment/rbs_loader.rb +21 -1
  59. data/lib/rigor/inference/block_call_timing.rb +7 -1
  60. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  61. data/lib/rigor/inference/captured_locals.rb +44 -13
  62. data/lib/rigor/inference/clause_widening.rb +90 -0
  63. data/lib/rigor/inference/definer_resolution.rb +87 -20
  64. data/lib/rigor/inference/expression_typer.rb +35 -5
  65. data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
  66. data/lib/rigor/inference/macro_block_self_type.rb +20 -6
  67. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +170 -35
  68. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
  69. data/lib/rigor/inference/method_dispatcher.rb +67 -3
  70. data/lib/rigor/inference/module_function_state.rb +137 -42
  71. data/lib/rigor/inference/narrowing.rb +54 -10
  72. data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
  73. data/lib/rigor/inference/project_patched_methods.rb +168 -4
  74. data/lib/rigor/inference/project_patched_mixins.rb +162 -0
  75. data/lib/rigor/inference/project_patched_scanner.rb +51 -12
  76. data/lib/rigor/inference/project_patched_visibility.rb +214 -0
  77. data/lib/rigor/inference/project_seed_tables.rb +120 -0
  78. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
  79. data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
  80. data/lib/rigor/inference/reflective_ivar_write.rb +6 -6
  81. data/lib/rigor/inference/return_barrier.rb +16 -0
  82. data/lib/rigor/inference/scope_indexer.rb +675 -105
  83. data/lib/rigor/inference/self_call_ivar_writes.rb +11 -6
  84. data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
  85. data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
  86. data/lib/rigor/inference/statement_evaluator.rb +446 -139
  87. data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
  88. data/lib/rigor/lens/contract_reader.rb +45 -2
  89. data/lib/rigor/lens/document.rb +2 -1
  90. data/lib/rigor/lens/file_reader.rb +12 -2
  91. data/lib/rigor/lens/members.rb +16 -2
  92. data/lib/rigor/lens/run.rb +34 -7
  93. data/lib/rigor/mcp/server.rb +2 -2
  94. data/lib/rigor/plugin/base.rb +3 -2
  95. data/lib/rigor/plugin/io_boundary.rb +42 -6
  96. data/lib/rigor/plugin/isolation.rb +4 -3
  97. data/lib/rigor/protection/discovery_seed.rb +9 -36
  98. data/lib/rigor/protection/mutation_cache.rb +8 -2
  99. data/lib/rigor/rooted_glob.rb +27 -0
  100. data/lib/rigor/scope/discovery_index.rb +5 -0
  101. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
  102. data/lib/rigor/scope/resolution_chain.rb +40 -5
  103. data/lib/rigor/scope.rb +45 -2
  104. data/lib/rigor/sig_gen/alias_index.rb +2 -1
  105. data/lib/rigor/sig_gen/classification.rb +4 -1
  106. data/lib/rigor/sig_gen/generator.rb +167 -22
  107. data/lib/rigor/sig_gen/layout_index.rb +5 -2
  108. data/lib/rigor/sig_gen/observation_collector.rb +137 -11
  109. data/lib/rigor/sig_gen/renderer.rb +13 -155
  110. data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
  111. data/lib/rigor/sig_gen/write_reporter.rb +203 -0
  112. data/lib/rigor/sig_gen/write_result.rb +3 -1
  113. data/lib/rigor/sig_gen/writer.rb +5 -3
  114. data/lib/rigor/signature_path_audit.rb +4 -2
  115. data/lib/rigor/type/constant.rb +10 -1
  116. data/lib/rigor/version.rb +1 -1
  117. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
  118. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +1 -1
  119. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
  120. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
  121. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  122. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
  123. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
  124. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
  125. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
  126. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
  127. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
  128. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  129. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  130. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +38 -9
  131. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
  132. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
  133. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
  134. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
  135. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
  136. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
  137. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  138. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
  139. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
  140. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
  141. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
  142. data/sig/rigor/plugin/io_boundary.rbs +5 -2
  143. data/sig/rigor/rooted_glob.rbs +7 -0
  144. data/sig/rigor/scope.rbs +4 -3
  145. data/skills/rigor-doctor/references/01-checks.md +23 -11
  146. data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
  147. metadata +13 -1
@@ -139,10 +139,11 @@ module Rigor
139
139
  # Issue #1596 — gives up when `self`'s class, a project subclass, or a project ancestor or module on either's
140
140
  # chain defines a {PROTOCOL_NAMES} method (a `def`, `define_method`, `attr_*` or alias), rewrites its methods
141
141
  # beyond the names it spells, or is refined on one; or when either chain holds an external ancestor, whose
142
- # own definitions the read cannot see.
142
+ # own definitions the read cannot see. Issue #1979 — a class built on a core constructor call
143
+ # (`class Fact < Data.define(:a)`) cannot be `self`'s class or a subclass of it, so the walk leaves it out.
143
144
  def no_protocol_override
144
145
  classes = [@class_name]
145
- RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name) { |subclass| classes << subclass }
146
+ RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name, possible_only: true) { |subclass| classes << subclass }
146
147
  classes.each do |class_name|
147
148
  chain = Scope::ResolutionChain.for(@scope, class_name.to_s, :instance, :methods)
148
149
  unprovable! if chain.truncated? || chain.wildcard_mixin? || !settled?(chain)
@@ -229,7 +230,7 @@ module Rigor
229
230
 
230
231
  # A subclass whose superclass the tables do not know is enumerated too, and its chain ends short of the
231
232
  # owner; one that reaches no definer of its own leaves the call to its superclass.
232
- RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name) do |subclass|
233
+ RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name, possible_only: true) do |subclass|
233
234
  unprovable! unless [owner, nil].include?(definer_ahead(subclass, name, owner))
234
235
  end
235
236
  def_node
@@ -258,9 +259,11 @@ module Rigor
258
259
  end
259
260
 
260
261
  def definition_count(owner, name)
261
- @scope.discovered_deferred_ranges.sum do |_path, rows|
262
- rows.count { |row| row[2] == name && row[3] == :instance && row[4] == owner }
262
+ count = 0
263
+ @scope.discovered_deferred_ranges.each_value do |rows|
264
+ count += rows.count { |row| row[2] == name && row[3] == :instance && row[4] == owner }
263
265
  end
266
+ count
264
267
  end
265
268
 
266
269
  # Whether `chain`'s own order stands with no second world: no order mark and no fork. `ResolutionChain#settle`
@@ -326,8 +329,10 @@ module Rigor
326
329
  return nil if loader.nil? || !loader.core_or_stdlib_class?(type.class_name)
327
330
 
328
331
  segment = node.name.to_s
332
+ suffix = "::#{segment}"
329
333
  project = [@scope.discovered_classes, @scope.in_source_constants]
330
- return nil if project.any? { |table| table.each_key.any? { |name| name.to_s.split("::").last == segment } }
334
+ ends_with_segment = ->(name) { (name = name.to_s) == segment || name.end_with?(suffix) }
335
+ return nil if project.any? { |table| table.each_key.any?(&ends_with_segment) }
331
336
 
332
337
  type
333
338
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Inference
5
+ # Issue #1572 — the singleton typing site's external-ahead decline. That site still answers from
6
+ # {Scope#singleton_def_through_ancestors}, which passes the chain's external entries over, so `extend M; extend
7
+ # Enumerable` typed `C.to_a` from `M#to_a` although Ruby calls `Enumerable#to_a` (`C.singleton_class.ancestors`
8
+ # starts `[#<Class:C>, Enumerable, M]`). The instance typing site reads `DefinerResolution`, which already
9
+ # declines there.
10
+ #
11
+ # {.withhold?} is true when the read must withhold (the call types `Dynamic`): the singleton chain holds no
12
+ # project entry that writes the name; the reader's `owner` has no project entry on it; or an external entry ahead
13
+ # of the later of the two may answer first, because RBS does not know it or declares the name in it (the test
14
+ # `DefinerResolution` applies, `ResolutionChain::Relevance.external_lacks_on?`). The later of the two, because
15
+ # where the chain does not stand the reader answers master's owner. An entry writes the name with its own `def`,
16
+ # never a copy the extends fold put on a class object (`RefinedDispatch::ClassObject.fold_copy?`, which also
17
+ # matches a copy by location where the two tables came from two parses of the file), so a folded `M#to_a`
18
+ # answers at `M`'s own position.
19
+ #
20
+ # ADR-46: the search files the class edge of the root and of every project entry ahead of the definer, and each
21
+ # tested external files the negative class edge on its spelling's last segment, as `DefinerResolution` does.
22
+ module SingletonExternalAhead
23
+ module_function
24
+
25
+ def withhold?(scope, class_name, method_name, owner)
26
+ chain = Scope::ResolutionChain.for(scope, class_name.to_s, :singleton, :methods)
27
+ definer = first_writer_index(scope, chain, method_name)
28
+ return true if definer.nil?
29
+
30
+ owner_index = chain.entries.index { |entry| !entry.external? && entry.name == owner.to_s }
31
+ return true if owner_index.nil?
32
+
33
+ external_ahead_may_answer?(scope, chain, method_name, [definer, owner_index].max)
34
+ end
35
+
36
+ def first_writer_index(scope, chain, method_name)
37
+ index = -1
38
+ chain.search(scope) do |entry|
39
+ index += 1
40
+ !entry.external? && writes_name?(scope, entry, method_name)
41
+ end && index
42
+ end
43
+
44
+ def writes_name?(scope, entry, method_name)
45
+ return !scope.user_def_for(entry.name, method_name).nil? unless entry.side == :singleton
46
+
47
+ !scope.singleton_def_for(entry.name, method_name).nil? &&
48
+ !RefinedDispatch::ClassObject.fold_copy?(scope, entry.name, method_name)
49
+ end
50
+
51
+ def external_ahead_may_answer?(scope, chain, method_name, stop)
52
+ recording = Analysis::DependencyRecorder.active?
53
+ chain.entries.first(stop).any? do |entry|
54
+ next false unless entry.external?
55
+
56
+ Analysis::DependencyRecorder.read_missing(:class, entry.raw.to_s.split("::").last) if recording
57
+ !Scope::ResolutionChain::Relevance.external_lacks_on?(scope, entry.candidates, entry.side, method_name)
58
+ end
59
+ end
60
+
61
+ private_class_method :first_writer_index, :writes_name?, :external_ahead_may_answer?
62
+ end
63
+ end
64
+ end
@@ -109,6 +109,13 @@ module Rigor
109
109
  nesting&.first
110
110
  end
111
111
 
112
+ # Issue #1722 — the hook-capable test for one chain entry, asked by `call.undefined-method` of a project module
113
+ # mixed into a core class: a module whose `self.included(base)` hook writes `base.class_eval { def x }` gives
114
+ # the includer methods no table records.
115
+ def entry_hook_capable?(scope, entry, flavor = :methods)
116
+ Context.new(scope, flavor).capable?(entry)
117
+ end
118
+
112
119
  def tested_levels(chain, from, bound)
113
120
  starts = chain.level_starts
114
121
  size = chain.entries.size