rigortype 0.4.3 → 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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/data/core_overlay/ostruct.rbs +40 -0
  4. data/docs/handbook/11-sig-gen.md +104 -15
  5. data/docs/manual/02-cli-reference.md +43 -12
  6. data/docs/manual/03-configuration.md +20 -7
  7. data/docs/manual/04-diagnostics.md +56 -3
  8. data/docs/manual/05-inspecting-types.md +2 -2
  9. data/docs/manual/06-baseline.md +1 -1
  10. data/docs/manual/07-plugins.md +3 -2
  11. data/docs/manual/08-skills.md +7 -0
  12. data/docs/manual/10-mcp-server.md +40 -4
  13. data/docs/manual/18-removing-dead-code.md +23 -2
  14. data/docs/manual/plugins/rigor-rails-routes.md +2 -2
  15. data/docs/manual/plugins/rigor-rspec.md +101 -1
  16. data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
  17. data/exe/rigor +12 -17
  18. data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
  19. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +111 -7
  20. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +190 -0
  21. data/lib/rigor/analysis/check_rules/rule_ids.rb +60 -1
  22. data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
  23. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +13 -4
  24. data/lib/rigor/analysis/check_rules.rb +274 -51
  25. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  26. data/lib/rigor/analysis/incremental.rb +61 -0
  27. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
  28. data/lib/rigor/analysis/incremental_session.rb +270 -28
  29. data/lib/rigor/analysis/path_expansion.rb +3 -1
  30. data/lib/rigor/analysis/reachability/graph.rb +111 -14
  31. data/lib/rigor/analysis/reachability/scan.rb +305 -23
  32. data/lib/rigor/analysis/rule_catalog.rb +22 -4
  33. data/lib/rigor/analysis/run_cache_key.rb +31 -7
  34. data/lib/rigor/analysis/run_cache_probe.rb +5 -3
  35. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +48 -12
  36. data/lib/rigor/analysis/runner/pool_coordinator.rb +179 -31
  37. data/lib/rigor/analysis/runner/project_pre_passes.rb +29 -4
  38. data/lib/rigor/analysis/runner.rb +102 -72
  39. data/lib/rigor/analysis/severity_stamp.rb +45 -1
  40. data/lib/rigor/analysis/worker_session.rb +69 -2
  41. data/lib/rigor/cache/descriptor.rb +79 -12
  42. data/lib/rigor/cache/engine_source.rb +3 -2
  43. data/lib/rigor/cache/incremental_snapshot.rb +39 -5
  44. data/lib/rigor/cache/rbs_descriptor.rb +2 -1
  45. data/lib/rigor/cache/store.rb +4 -3
  46. data/lib/rigor/cli/command.rb +3 -1
  47. data/lib/rigor/cli/docs_command.rb +3 -2
  48. data/lib/rigor/cli/doctor_command.rb +1 -1
  49. data/lib/rigor/cli/explain_command.rb +3 -2
  50. data/lib/rigor/cli/mcp_command.rb +1 -1
  51. data/lib/rigor/cli/plugin_command.rb +2 -1
  52. data/lib/rigor/cli/plugins_command.rb +2 -1
  53. data/lib/rigor/cli/sig_gen_command.rb +32 -11
  54. data/lib/rigor/cli/skill_command.rb +2 -1
  55. data/lib/rigor/cli/skill_describe.rb +3 -2
  56. data/lib/rigor/cli/unused_command.rb +10 -7
  57. data/lib/rigor/cli.rb +3 -2
  58. data/lib/rigor/config_audit.rb +163 -22
  59. data/lib/rigor/configuration/dependencies.rb +19 -11
  60. data/lib/rigor/configuration/severity_profile.rb +20 -2
  61. data/lib/rigor/configuration.rb +29 -3
  62. data/lib/rigor/effects/signature_sources.rb +3 -1
  63. data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
  64. data/lib/rigor/environment/gated_signature_guard.rb +2 -1
  65. data/lib/rigor/environment/gated_signature_plan.rb +2 -1
  66. data/lib/rigor/environment/installed_gem_set.rb +2 -1
  67. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
  68. data/lib/rigor/environment/rbs_loader.rb +67 -11
  69. data/lib/rigor/inference/block_call_timing.rb +12 -1
  70. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  71. data/lib/rigor/inference/captured_locals.rb +67 -13
  72. data/lib/rigor/inference/clause_widening.rb +90 -0
  73. data/lib/rigor/inference/definer_resolution.rb +101 -25
  74. data/lib/rigor/inference/expression_typer.rb +94 -9
  75. data/lib/rigor/inference/guard_rebinding.rb +7 -2
  76. data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
  77. data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
  78. data/lib/rigor/inference/macro_block_self_type.rb +20 -6
  79. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +170 -35
  80. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
  81. data/lib/rigor/inference/method_dispatcher.rb +67 -3
  82. data/lib/rigor/inference/module_function_state.rb +213 -51
  83. data/lib/rigor/inference/narrowing.rb +235 -75
  84. data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
  85. data/lib/rigor/inference/project_patched_methods.rb +168 -4
  86. data/lib/rigor/inference/project_patched_mixins.rb +162 -0
  87. data/lib/rigor/inference/project_patched_scanner.rb +51 -12
  88. data/lib/rigor/inference/project_patched_visibility.rb +214 -0
  89. data/lib/rigor/inference/project_seed_tables.rb +120 -0
  90. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
  91. data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
  92. data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
  93. data/lib/rigor/inference/repeated_or_writes.rb +414 -1
  94. data/lib/rigor/inference/return_barrier.rb +16 -0
  95. data/lib/rigor/inference/scope_indexer.rb +1061 -122
  96. data/lib/rigor/inference/self_call_ivar_writes.rb +346 -0
  97. data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
  98. data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
  99. data/lib/rigor/inference/statement_evaluator.rb +512 -150
  100. data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
  101. data/lib/rigor/lens/anchor.rb +3 -3
  102. data/lib/rigor/lens/contract_reader.rb +60 -2
  103. data/lib/rigor/lens/document.rb +4 -3
  104. data/lib/rigor/lens/file_reader.rb +452 -73
  105. data/lib/rigor/lens/members.rb +81 -17
  106. data/lib/rigor/lens/run.rb +73 -4
  107. data/lib/rigor/lens.rb +2 -1
  108. data/lib/rigor/mcp/server.rb +63 -22
  109. data/lib/rigor/plugin/base.rb +3 -2
  110. data/lib/rigor/plugin/box_probe.rb +154 -7
  111. data/lib/rigor/plugin/io_boundary.rb +42 -6
  112. data/lib/rigor/plugin/isolation.rb +4 -3
  113. data/lib/rigor/project_environment.rb +6 -3
  114. data/lib/rigor/protection/discovery_seed.rb +9 -36
  115. data/lib/rigor/protection/mutation_cache.rb +8 -2
  116. data/lib/rigor/rooted_glob.rb +27 -0
  117. data/lib/rigor/scope/discovery_index.rb +5 -0
  118. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
  119. data/lib/rigor/scope/resolution_chain.rb +40 -5
  120. data/lib/rigor/scope.rb +56 -8
  121. data/lib/rigor/sig_gen/alias_index.rb +2 -1
  122. data/lib/rigor/sig_gen/classification.rb +4 -1
  123. data/lib/rigor/sig_gen/generator.rb +167 -22
  124. data/lib/rigor/sig_gen/layout_index.rb +40 -1
  125. data/lib/rigor/sig_gen/method_candidate.rb +8 -0
  126. data/lib/rigor/sig_gen/observation_collector.rb +137 -11
  127. data/lib/rigor/sig_gen/path_mapper.rb +18 -2
  128. data/lib/rigor/sig_gen/renderer.rb +13 -155
  129. data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
  130. data/lib/rigor/sig_gen/write_reporter.rb +203 -0
  131. data/lib/rigor/sig_gen/write_result.rb +3 -1
  132. data/lib/rigor/sig_gen/writer.rb +50 -35
  133. data/lib/rigor/signature_path_audit.rb +4 -2
  134. data/lib/rigor/type/constant.rb +10 -1
  135. data/lib/rigor/version.rb +1 -1
  136. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
  137. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +1 -1
  138. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
  139. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
  140. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  141. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
  142. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
  143. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
  144. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
  145. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
  146. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
  147. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
  148. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  149. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
  150. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  151. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +57 -6
  152. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
  153. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
  154. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
  155. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
  156. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
  157. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
  158. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
  159. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
  160. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  161. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
  162. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
  163. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
  164. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
  165. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
  166. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
  167. data/sig/rigor/plugin/io_boundary.rbs +5 -2
  168. data/sig/rigor/rooted_glob.rbs +7 -0
  169. data/sig/rigor/scope.rbs +4 -3
  170. data/skills/rigor-doctor/references/01-checks.md +23 -11
  171. data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
  172. metadata +16 -1
@@ -23,7 +23,9 @@ module Rigor
23
23
  # - a statement, and the body of `(…)`, `begin` / `rescue` / `else` / `ensure`;
24
24
  # - an assignment's value — every variable-write form, and an index `||=` / `&&=` / `op=`, but not its
25
25
  # receiver or index arguments;
26
- # - an `if` / `unless` / `case` predicate and every branch, but not a `when` condition or an `in` guard;
26
+ # - an `if` / `unless` / `case` predicate and every branch, but not a `when` condition or an `in` guard (since
27
+ # issue #1963 the evaluator threads a `when` test that runs code in a `case` with a subject; the scan does
28
+ # not admit it yet, which floors a name needlessly rather than letting a pin through);
27
29
  # - both operands of `&&` / `||`;
28
30
  # - a `while` / `until` predicate and body, a `for` index, collection and body;
29
31
  # - a multi-assign value and its local and instance-variable targets, and the LOCAL a `rescue => e`
@@ -32,9 +32,9 @@ module Rigor
32
32
  end
33
33
 
34
34
  # The anchor of the declaration spanning bytes `start_offset...end_offset` and lines `start_line..end_line`.
35
- def for_span(start_offset, end_offset, start_line, end_line)
36
- leading = leading_contract_comments(start_line)
37
- trailing = trailing_contract_comment(end_line, end_offset)
35
+ def for_span(start_offset, end_offset, start_line, end_line, leading: true, trailing: true)
36
+ leading = leading ? leading_contract_comments(start_line) : []
37
+ trailing = trailing ? trailing_contract_comment(end_line, end_offset) : nil
38
38
  material = +"".b
39
39
  leading.each { |comment| material << line_text_from(comment.location.start_offset) << "\n" }
40
40
  material << @source.byteslice(start_offset, end_offset - start_offset)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "slot"
4
+ require_relative "parameters"
4
5
  require_relative "../reflection"
5
6
 
6
7
  module Rigor
@@ -34,6 +35,41 @@ module Rigor
34
35
  unreadable("definition_build_failed", [])
35
36
  end
36
37
 
38
+ # The block an otherwise defaulted inline declaration (`rigor:v1:inferred-signature`) declares, as a
39
+ # `{required, type}` slot, or nil. The rbs-inline plugin defaults an unannotated `&block` to `?{ (?) ->
40
+ # untyped }`, so any other block came from a `# @rbs &block:` annotation, and `check` binds the block's
41
+ # parameters from it. A block spelled like the default carries nothing either way.
42
+ def read_defaulted_block(owner, scope, name, lookup_scope)
43
+ method = lookup(owner, scope, name, lookup_scope)
44
+ defs = method ? method.defs.select { |definition| definition.defined_in.to_s.delete_prefix("::") == owner } : []
45
+ return nil unless defs.size == 1 && annotations_of(method, defs).include?(INFERRED_SIGNATURE)
46
+
47
+ block = defs.first.type.block
48
+ return nil if block.nil? || default_block?(block)
49
+
50
+ provenance, path = @provenance.classify(buffer_name(defs.first))
51
+ return nil if provenance.nil?
52
+
53
+ { "required" => block.required, "type" => Slot.from_rbs(block_type(block), provenance, path) }
54
+ rescue StandardError
55
+ nil
56
+ end
57
+
58
+ # The declared type of the constant `name` (qualified), as a slot with its declaration's provenance, or nil
59
+ # when no RBS or inline declaration names it.
60
+ def read_constant(name, environment)
61
+ declaration = environment&.rbs_loader&.constant_declaration(name)
62
+ return nil if declaration.nil?
63
+
64
+ type, buffer = declaration
65
+ provenance, path = @provenance.classify(buffer)
66
+ return Slot.unavailable("not_computed") if provenance.nil?
67
+
68
+ Slot.from_rbs(type, provenance, path)
69
+ rescue StandardError
70
+ Slot.unavailable("not_computed")
71
+ end
72
+
37
73
  private
38
74
 
39
75
  def contract(defs, annotations)
@@ -87,14 +123,36 @@ module Rigor
87
123
  def overload(method_type, provenance, path, return_inferred, annotations)
88
124
  function = method_type.type
89
125
  params = function.respond_to?(:required_positionals) ? params_of(function, provenance, path) : nil
90
- block = method_type.block && { "required" => method_type.block.required,
91
- "type" => Slot.from_rbs(method_type.block.type.to_s, provenance, path) }
126
+ partly_defaulted = return_inferred && provenance == "inline"
127
+ block = method_type.block && block_slot(method_type.block, provenance, path, partly_defaulted)
92
128
  prefix = annotations.map { |string| "%a{#{string}} " }.join
93
129
  return_slot = return_inferred ? nil : Slot.from_rbs(function.return_type.to_s, provenance, path)
94
130
  { "rbs" => "#{prefix}#{method_type}", "params" => params, "block" => block, "return" => return_slot,
95
131
  "return_rbs" => function.return_type.to_s }
96
132
  end
97
133
 
134
+ # A block's function type as `RBS::MethodType#to_s` spells it inside the braces (`(Integer) -> void`, with a
135
+ # `[self: T]` binding when declared). Neither `RBS::Types::Function` nor `UntypedFunction` has a `to_s`.
136
+ def block_type(block)
137
+ self_binding = ::RBS::Types::SelfTypeBindingHelper.self_type_binding_to_s(block.self_type)
138
+ "(#{block.type.param_to_s}) #{self_binding}-> #{block.type.return_to_s}"
139
+ end
140
+
141
+ # Under an inline declaration the rbs-inline plugin defaulted in part (`partly_defaulted`), a block shaped
142
+ # like its default was not written: it reads as an undeclared block does.
143
+ def block_slot(block, provenance, path, partly_defaulted)
144
+ if partly_defaulted && default_block?(block)
145
+ return { "required" => false, "type" => Slot.dynamic("inferred", Parameters::UNDECLARED) }
146
+ end
147
+
148
+ { "required" => block.required, "type" => Slot.from_rbs(block_type(block), provenance, path) }
149
+ end
150
+
151
+ def default_block?(block)
152
+ !block.required && block.type.is_a?(::RBS::Types::UntypedFunction) && block.self_type.nil? &&
153
+ block.type.return_type.is_a?(::RBS::Types::Bases::Any)
154
+ end
155
+
98
156
  def params_of(function, provenance, path)
99
157
  (positional_params(function) + keyword_params(function)).map do |kind, name, param|
100
158
  { "name" => name&.to_s, "kind" => kind, "type" => Slot.from_rbs(param.type.to_s, provenance, path) }
@@ -5,13 +5,14 @@ require "json"
5
5
  require_relative "../version"
6
6
  require_relative "../cache/engine_source"
7
7
  require_relative "../analysis/run_cache_key"
8
+ require_relative "../rooted_glob"
8
9
 
9
10
  module Rigor
10
11
  module Lens
11
12
  # Assembles the `lens/v0` document (docs/internal-spec/lens.md § Document) from a {Run::Outcome}.
12
13
  class Document
13
- SITE_FIELDS = %w[key kind name owner scope attribute members ordinal span contract_span anchor visibility
14
- contract params block body_return].freeze
14
+ SITE_FIELDS = %w[key kind name owner scope attribute members module_function builder ordinal span contract_span
15
+ anchor visibility contract params block body_return value alias_of].freeze
15
16
 
16
17
  def initialize(outcome:, configuration:, root:, config_path:)
17
18
  @outcome = outcome
@@ -60,7 +61,7 @@ module Rigor
60
61
  end
61
62
 
62
63
  def signature_digest
63
- listing = signature_roots.flat_map { |root| Dir.glob(File.join(root, "**", "*.rbs")) }.sort.map do |path|
64
+ listing = signature_roots.flat_map { |root| RootedGlob.glob(root, "**/*.rbs") }.sort.map do |path|
64
65
  "#{relative(path)}\t#{Lens.digest(File.binread(path))}\n"
65
66
  end
66
67
  Lens.digest(listing.join)