rigortype 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/builtins/ruby_core/io.yml +1 -0
  4. data/data/builtins/ruby_core/proc.yml +4 -0
  5. data/data/builtins/ruby_core/random.yml +4 -0
  6. data/data/builtins/ruby_core/struct.yml +3 -0
  7. data/data/core_overlay/env.rbs +34 -0
  8. data/data/core_overlay/gc.rbs +50 -0
  9. data/data/core_overlay/hash.rbs +45 -0
  10. data/data/core_overlay/integer.rbs +31 -0
  11. data/data/core_overlay/io_buffer.rbs +27 -0
  12. data/data/core_overlay/kernel.rbs +40 -0
  13. data/data/core_overlay/match_data.rbs +28 -0
  14. data/data/core_overlay/module.rbs +51 -0
  15. data/data/core_overlay/object_space.rbs +29 -0
  16. data/data/core_overlay/proc.rbs +45 -0
  17. data/data/core_overlay/range.rbs +40 -0
  18. data/data/core_overlay/source_range.rbs +78 -0
  19. data/data/core_overlay/string.rbs +86 -0
  20. data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
  21. data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
  22. data/docs/handbook/01-getting-started.md +3 -3
  23. data/docs/handbook/06-classes.md +3 -1
  24. data/docs/handbook/11-sig-gen.md +6 -0
  25. data/docs/handbook/appendix-liskov.md +5 -0
  26. data/docs/manual/03-configuration.md +1 -1
  27. data/docs/manual/04-diagnostics.md +2 -1
  28. data/docs/manual/06-baseline.md +2 -0
  29. data/docs/manual/07-plugins.md +1 -1
  30. data/docs/manual/18-removing-dead-code.md +32 -2
  31. data/docs/manual/plugins/README.md +6 -1
  32. data/docs/manual/plugins/rigor-activerecord.md +21 -0
  33. data/docs/manual/plugins/rigor-alba.md +60 -0
  34. data/docs/manual/plugins/rigor-minitest.md +22 -4
  35. data/docs/manual/plugins/rigor-typelizer.md +71 -0
  36. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
  37. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
  38. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
  39. data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
  40. data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
  41. data/lib/rigor/analysis/check_rules.rb +289 -57
  42. data/lib/rigor/analysis/dependency_recorder.rb +15 -0
  43. data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
  44. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
  45. data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
  46. data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
  47. data/lib/rigor/analysis/erb_template_detector.rb +2 -1
  48. data/lib/rigor/analysis/incremental.rb +29 -9
  49. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
  50. data/lib/rigor/analysis/incremental_run_slot.rb +3 -1
  51. data/lib/rigor/analysis/incremental_session.rb +14 -1
  52. data/lib/rigor/analysis/reachability/graph.rb +123 -23
  53. data/lib/rigor/analysis/reachability/scan.rb +290 -36
  54. data/lib/rigor/analysis/rule_catalog.rb +43 -0
  55. data/lib/rigor/analysis/run_cache_key.rb +16 -1
  56. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
  57. data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
  58. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  59. data/lib/rigor/analysis/runner.rb +34 -3
  60. data/lib/rigor/analysis/worker_session.rb +7 -4
  61. data/lib/rigor/cache/incremental_snapshot.rb +22 -0
  62. data/lib/rigor/cli/annotate_command.rb +5 -0
  63. data/lib/rigor/cli/baseline_command.rb +7 -18
  64. data/lib/rigor/cli/check_command.rb +2 -1
  65. data/lib/rigor/cli/trace_command.rb +5 -0
  66. data/lib/rigor/cli/type_of_command.rb +5 -3
  67. data/lib/rigor/cli/unused_command.rb +13 -7
  68. data/lib/rigor/cli.rb +10 -2
  69. data/lib/rigor/configuration/severity_profile.rb +6 -0
  70. data/lib/rigor/configuration.rb +46 -2
  71. data/lib/rigor/environment/gated_signature_guard.rb +43 -0
  72. data/lib/rigor/environment/gated_signature_plan.rb +339 -0
  73. data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
  74. data/lib/rigor/environment/rbs_loader.rb +283 -14
  75. data/lib/rigor/environment/required_features.rb +206 -0
  76. data/lib/rigor/environment.rb +21 -3
  77. data/lib/rigor/inference/acceptance.rb +28 -3
  78. data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
  79. data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
  80. data/lib/rigor/inference/def_node_resolver.rb +23 -1
  81. data/lib/rigor/inference/expression_typer.rb +215 -16
  82. data/lib/rigor/inference/global_write_census.rb +7 -4
  83. data/lib/rigor/inference/guard_rebinding.rb +1 -1
  84. data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
  85. data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
  86. data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
  87. data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
  88. data/lib/rigor/inference/in_effect_refinements.rb +256 -0
  89. data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
  90. data/lib/rigor/inference/key_presence_guard.rb +303 -0
  91. data/lib/rigor/inference/macro_block_self_type.rb +59 -12
  92. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  93. data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
  94. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
  95. data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
  96. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
  97. data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
  98. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
  99. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
  100. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +259 -28
  101. data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
  102. data/lib/rigor/inference/method_dispatcher.rb +44 -11
  103. data/lib/rigor/inference/module_function_state.rb +2 -1
  104. data/lib/rigor/inference/narrowing.rb +20 -6
  105. data/lib/rigor/inference/object_mixins.rb +180 -0
  106. data/lib/rigor/inference/project_patched_methods.rb +10 -5
  107. data/lib/rigor/inference/project_patched_scanner.rb +71 -3
  108. data/lib/rigor/inference/refine_census.rb +239 -0
  109. data/lib/rigor/inference/refine_self.rb +317 -0
  110. data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
  111. data/lib/rigor/inference/refined_dispatch.rb +272 -0
  112. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
  113. data/lib/rigor/inference/scope_indexer.rb +687 -60
  114. data/lib/rigor/inference/statement_evaluator.rb +71 -20
  115. data/lib/rigor/inference/string_mutation.rb +9 -3
  116. data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
  117. data/lib/rigor/inference/version_guard.rb +19 -10
  118. data/lib/rigor/language_server/project_context.rb +31 -0
  119. data/lib/rigor/language_server/server.rb +18 -0
  120. data/lib/rigor/plugin/inflector.rb +1 -1
  121. data/lib/rigor/plugin/io_boundary.rb +42 -3
  122. data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
  123. data/lib/rigor/project_environment.rb +29 -6
  124. data/lib/rigor/protection/mutation_cache.rb +1 -1
  125. data/lib/rigor/protection/mutator.rb +1 -1
  126. data/lib/rigor/reflection/constant_ancestors.rb +127 -11
  127. data/lib/rigor/reflection/constant_path.rb +13 -2
  128. data/lib/rigor/reflection.rb +40 -8
  129. data/lib/rigor/scope/discovery_index.rb +40 -1
  130. data/lib/rigor/scope/resolution_chain.rb +159 -11
  131. data/lib/rigor/scope.rb +189 -10
  132. data/lib/rigor/sig_gen/classification.rb +4 -1
  133. data/lib/rigor/sig_gen/generator.rb +55 -5
  134. data/lib/rigor/sig_gen/method_candidate.rb +2 -2
  135. data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
  136. data/lib/rigor/source/alias_names.rb +52 -0
  137. data/lib/rigor/source/literals.rb +3 -2
  138. data/lib/rigor/source/symbol_name.rb +20 -0
  139. data/lib/rigor/type/combinator.rb +34 -2
  140. data/lib/rigor/type/float_range.rb +4 -1
  141. data/lib/rigor/type/nominal.rb +6 -1
  142. data/lib/rigor/version.rb +1 -1
  143. data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
  144. data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
  145. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
  146. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
  147. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
  148. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
  149. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
  150. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
  151. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
  152. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
  153. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
  154. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
  155. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
  156. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
  157. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
  158. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
  159. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
  160. data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
  161. data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
  162. data/plugins/rigor-alba/sig/alba.rbs +4 -0
  163. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
  164. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
  165. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
  166. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
  167. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
  168. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
  169. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
  170. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
  171. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
  172. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
  173. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
  174. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
  175. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
  176. data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
  177. data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
  178. data/sig/rigor/plugin/io_boundary.rbs +3 -0
  179. data/sig/rigor/scope.rbs +27 -4
  180. data/sig/rigor/source.rbs +4 -0
  181. data/sig/rigor.rbs +3 -1
  182. metadata +66 -1
@@ -6,6 +6,7 @@ require_relative "../reflection"
6
6
  require_relative "../source/node_walker"
7
7
  require_relative "../source/constant_path"
8
8
  require_relative "../inference/singleton_object_constant"
9
+ require_relative "../inference/object_mixins"
9
10
  require_relative "shadow_harness"
10
11
  require_relative "../type"
11
12
  require_relative "diagnostic"
@@ -29,6 +30,7 @@ require_relative "check_rules/self_closedness_scanner"
29
30
  require_relative "check_rules/source_arity"
30
31
  require_relative "check_rules/lexical_method_sites"
31
32
  require_relative "check_rules/special_global_setters"
33
+ require_relative "check_rules/ruby_deprecations"
32
34
 
33
35
  module Rigor
34
36
  module Analysis
@@ -66,6 +68,17 @@ module Rigor
66
68
  # resolution chain's.
67
69
  VISIBILITY_RANK = { public: 2, protected: 1, private: 0 }.freeze
68
70
 
71
+ # ADR-35 WD10 (issue #1716) — the object-lifecycle hooks the three override rules skip. `initialize` is
72
+ # reached through `Class#new` on the class the caller names, and the copy hooks are handed an instance
73
+ # of the receiver's own class by `dup` / `clone`, so neither is called with the PARENT's declared
74
+ # arguments through a parent-typed reference; `new` / `dup` / `clone` discard all four returns.
75
+ LIFECYCLE_HOOKS = %i[initialize initialize_copy initialize_dup initialize_clone].freeze
76
+
77
+ # The names CRuby makes private on every non-singleton `def` whatever section it is written in
78
+ # (`vm_method.c` `rb_method_entry_make`), so a source-discovered visibility for them is not the
79
+ # runtime one and `def.override-visibility-reduced` cannot prove a reduction.
80
+ IMPLICITLY_PRIVATE_METHODS = (LIFECYCLE_HOOKS + %i[respond_to_missing?]).freeze
81
+
69
82
  # Resolves a user-supplied rule token (`undefined-method`,
70
83
  # `call.undefined-method`, or the family wildcard `call`)
71
84
  # to the set of canonical rule identifiers it disables.
@@ -96,8 +109,11 @@ module Rigor
96
109
  # direct caller with no plugin walk, e.g. a unit test), the standalone
97
110
  # {RuleWalk} walk runs here instead, so `diagnose` stays correct
98
111
  # without the converged path.
99
- def diagnose(path:, root:, scope_index:, self_call_misses: [], comments: [], disabled_rules: [],
100
- node_collectors: nil)
112
+ #
113
+ # @param stated_runtime_ruby — {Configuration#stated_runtime_ruby}: the explicit `target_ruby`, or nil. Only
114
+ # {RubyDeprecations} reads it, and it walks the file only when the stated Ruby deprecates something.
115
+ def diagnose(path:, root:, scope_index:, self_call_misses: [], comments: [], disabled_rules: [], # rubocop:disable Metrics/ParameterLists
116
+ node_collectors: nil, stated_runtime_ruby: nil)
101
117
  collectors = node_collectors || run_node_collectors(path, root, scope_index)
102
118
  diagnostics = collectors[:main_pass].results.dup
103
119
  diagnostics.concat(self_undefined_method_diagnostics(path, self_call_misses, root, scope_index))
@@ -109,6 +125,9 @@ module Rigor
109
125
  # `suppression.*` joins the list, which stays reportable inside a dead arm — a malformed
110
126
  # `# rigor:disable` marker is an authoring error regardless of whether its code runs.
111
127
  diagnostics = DeadVersionGuardArms.filter(diagnostics, root)
128
+ # The ADR-47 WD5 amendment (#1692) — joined after that filter: the deprecation rule reads each version guard
129
+ # against the stated Ruby itself, and the analyzer's own Ruby says nothing about where the call runs.
130
+ diagnostics.concat(RubyDeprecations.diagnostics(path, root, scope_index, stated_runtime_ruby))
112
131
  # Suppression-marker validation (`suppression.*`) runs BEFORE the filter so its own diagnostics are
113
132
  # suppressible like any other rule — `# rigor:disable suppression.unknown-rule` on the offending
114
133
  # comment's line works, with no regress (the token itself is known, so it never re-fires).
@@ -166,7 +185,7 @@ module Rigor
166
185
  diagnostic = return_type_mismatch_diagnostic(path, node, scope_index)
167
186
  return diagnostic if diagnostic.nil? || lexical_sites.nil?
168
187
 
169
- lexical_sites.refinement_def?(node) ? nil : diagnostic
188
+ lexical_sites.refinements.refinement_def?(node) ? nil : diagnostic
170
189
  end
171
190
 
172
191
  # Constructs the fresh, unpopulated built-in collector set keyed by
@@ -178,7 +197,7 @@ module Rigor
178
197
  # diagnostics carry it (ADR-53 B3c hosts it on the same walk).
179
198
  def build_node_collectors(path, scope_index, root = nil)
180
199
  eval_ranges = receiver_eval_block_ranges(root)
181
- lexical_sites = LexicalMethodSites.new(root)
200
+ lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
182
201
  main_pass = ->(node) { main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites) }
183
202
  {
184
203
  main_pass: MainPassCollector.new(main_pass),
@@ -252,6 +271,14 @@ module Rigor
252
271
  collector.class.new(scope_index).collect(root)
253
272
  end
254
273
 
274
+ # Issue #1673 — the in-effect refinements `Inference::ScopeIndexer` stamped for `root`, read off the root's scope
275
+ # so the rules answer from the query the typer reads; nil where the index holds no scope for it.
276
+ def stamped_refinements(scope_index, root)
277
+ return nil if root.nil? || scope_index.nil?
278
+
279
+ scope_index[root]&.discovery&.in_effect_refinements
280
+ end
281
+
255
282
  # The former inline main pass, kept as the shadow oracle: walks the
256
283
  # tree with `Source::NodeWalker.each` and accumulates the same
257
284
  # per-node diagnostics in the same order {MainPassCollector} now
@@ -259,7 +286,7 @@ module Rigor
259
286
  def main_pass_oracle(path, root, scope_index)
260
287
  diagnostics = []
261
288
  eval_ranges = receiver_eval_block_ranges(root)
262
- lexical_sites = LexicalMethodSites.new(root)
289
+ lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
263
290
  Source::NodeWalker.each(root) do |node|
264
291
  diagnostics.concat(main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites))
265
292
  end
@@ -284,15 +311,15 @@ module Rigor
284
311
  def call_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil)
285
312
  [
286
313
  undefined_method_diagnostic(path, node, scope_index, lexical_sites),
287
- unresolved_toplevel_diagnostic(path, node, scope_index, eval_ranges),
288
- wrong_arity_diagnostic(path, node, scope_index),
289
- argument_type_diagnostic(path, node, scope_index),
290
- nil_receiver_diagnostic(path, node, scope_index),
314
+ unresolved_toplevel_diagnostic(path, node, scope_index, eval_ranges, lexical_sites),
315
+ wrong_arity_diagnostic(path, node, scope_index, lexical_sites),
316
+ argument_type_diagnostic(path, node, scope_index, lexical_sites),
317
+ nil_receiver_diagnostic(path, node, scope_index, lexical_sites),
291
318
  dump_type_diagnostic(path, node, scope_index),
292
319
  assert_type_diagnostic(path, node, scope_index),
293
- always_raises_diagnostic(path, node, scope_index),
320
+ always_raises_diagnostic(path, node, scope_index, lexical_sites),
294
321
  raise_non_exception_diagnostic(path, node, scope_index),
295
- visibility_mismatch_diagnostic(path, node, scope_index)
322
+ visibility_mismatch_diagnostic(path, node, scope_index, lexical_sites)
296
323
  ].compact
297
324
  end
298
325
 
@@ -316,6 +343,10 @@ module Rigor
316
343
  # Class-level ivars (`@x = 1` outside any def, in the
317
344
  # class body) are also skipped — they're a separate
318
345
  # surface (`Module#@var`) the engine doesn't yet model.
346
+ # - A divergence the class's RBS declaration admits is withheld: when the ivar is declared (`@x: T`,
347
+ # or the slot an `attr_*` member implies) and that type accepts both the first write and the
348
+ # later one, the union is the author's intent. The declaration only silences here; checking
349
+ # writes against it is #1406.
319
350
  def ivar_write_mismatch_diagnostics(path, ivar_writes)
320
351
  ivar_writes.flat_map do |class_name, writes_by_ivar|
321
352
  writes_by_ivar.flat_map do |ivar_name, writes|
@@ -427,11 +458,18 @@ module Rigor
427
458
  writes[(canonical_index + 1)..].filter_map do |write|
428
459
  other_class = ivar_class_for(write[:type])
429
460
  next nil if other_class.nil? || other_class == "NilClass" || other_class == first_class
461
+ next nil if declaration_admits?(write[:declared], canonical[:type], write[:type])
430
462
 
431
463
  build_ivar_write_mismatch_diagnostic(path, write[:node], class_name, ivar_name, first_class, other_class)
432
464
  end
433
465
  end
434
466
 
467
+ def declaration_admits?(declared, *write_types)
468
+ return false if declared.nil?
469
+
470
+ write_types.none? { |type| Inference::Acceptance.accepts(declared, type, mode: :gradual).no? }
471
+ end
472
+
435
473
  # v0.0.2 #6 — diagnostic suppression. Three kinds of
436
474
  # suppression compose:
437
475
  #
@@ -516,7 +554,7 @@ module Rigor
516
554
  line_suppressions = Hash.new { |h, k| h[k] = Set.new }
517
555
  file_suppressions = Set.new
518
556
  comments.each do |comment|
519
- source = comment.location.slice
557
+ source = comment_text(comment)
520
558
  if (match = FILE_SUPPRESSION_PATTERN.match(source))
521
559
  absorb_suppression_tokens(match[:rules], file_suppressions)
522
560
  elsif (match = LINE_SUPPRESSION_PATTERN.match(source))
@@ -526,6 +564,15 @@ module Rigor
526
564
  [line_suppressions, file_suppressions]
527
565
  end
528
566
 
567
+ # The comment's text for the suppression regexps. A comment is not validated by Ruby, so it may hold a
568
+ # byte that is invalid in the file's encoding, and a regexp raises on such a string; the marker grammar
569
+ # is ASCII, so scrubbing the bad bytes loses nothing. Positions come from `comment.location`, which is
570
+ # byte-offset based and untouched.
571
+ def comment_text(comment)
572
+ text = comment.location.slice
573
+ text.valid_encoding? ? text : text.scrub("?")
574
+ end
575
+
529
576
  def absorb_suppression_tokens(raw, target)
530
577
  raw.to_s.split(/[\s,]+/).reject(&:empty?).each do |token|
531
578
  target.merge(expand_token(token))
@@ -543,7 +590,7 @@ module Rigor
543
590
  # that token is known, so acknowledging it never re-fires the rule.
544
591
  def suppression_marker_diagnostics(path, comments)
545
592
  comments.each_with_object([]) do |comment, diagnostics|
546
- source = comment.location.slice
593
+ source = comment_text(comment)
547
594
  if (match = FILE_SUPPRESSION_PATTERN.match(source))
548
595
  validate_suppression_tokens(match[:rules], "rigor:disable-file", path, comment, diagnostics)
549
596
  elsif (match = LINE_SUPPRESSION_PATTERN.match(source))
@@ -678,13 +725,13 @@ module Rigor
678
725
  class << self
679
726
  private
680
727
 
681
- def undefined_method_diagnostic(path, call_node, scope_index, lexical_sites = nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
728
+ def undefined_method_diagnostic(path, call_node, scope_index, lexical_sites = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
682
729
  return nil if call_node.receiver.nil?
683
730
 
684
731
  scope = scope_index[call_node]
685
732
  return nil if scope.nil?
686
733
 
687
- # The two exemptions that hold whatever shape the receiver has. Ahead of the receiver-shape
734
+ # The exemptions that hold whatever shape the receiver has. Ahead of the receiver-shape
688
735
  # branch below because a plugin is consulted ONCE for the whole receiver, union included, so the
689
736
  # gate belongs where that one consult is rather than duplicated into each shape's path.
690
737
  #
@@ -722,12 +769,18 @@ module Rigor
722
769
  # the recorded name only: `Merger.nope` still fires.
723
770
  return nil if Inference::SingletonObjectConstant.recorded?(call_node, receiver_type, call_node.name, scope)
724
771
 
772
+ # Issue #1699 — judged as its one class; the message still names the union. See
773
+ # {#single_class_literal_union}.
774
+ rendered_receiver = receiver_type
775
+ receiver_type = single_class_literal_union(receiver_type)
725
776
  class_name = concrete_class_name(receiver_type)
726
777
  # A union receiver has no single concrete class. The scalar path
727
778
  # below cannot reason about it, but the call is still definitely
728
779
  # undefined when EVERY arm lacks the method — see
729
780
  # `union_undefined_method_diagnostic`.
730
- return union_undefined_method_diagnostic(path, call_node, receiver_type, scope) if class_name.nil?
781
+ if class_name.nil?
782
+ return union_undefined_method_diagnostic(path, call_node, receiver_type, scope, lexical_sites)
783
+ end
731
784
 
732
785
  # ADR-26 — a plugin may declare a class "open": one
733
786
  # known to respond beyond its RBS-declared method
@@ -782,50 +835,107 @@ module Rigor
782
835
  # `call.undefined-method` into 171 through exactly this reading, 49 of them here.
783
836
  return nil if project_sidecar_owns_method?(scope, class_name, call_node.name, kind)
784
837
 
785
- build_undefined_method_diagnostic(path, call_node, receiver_type, definition_site, class_name)
838
+ build_undefined_method_diagnostic(path, call_node, rendered_receiver, definition_site, class_name)
839
+ end
840
+
841
+ # Issue #1717 — an explicit `self.m` / `self.m = v` inside a block whose `self` the engine does not know
842
+ # (`Scope#block_self_unknown?`, set where issue #316's opaque mark is). The receiver still types as the
843
+ # ENCLOSING `self`, but the yielding method may `instance_exec` the block on another object
844
+ # (`ActionController::Renderers.add` runs its block on the controller), so a method missing on the
845
+ # enclosing `self` is no evidence. The implicit `m` in the same block is already silent; the explicit
846
+ # spelling must not be stricter. A block
847
+ # whose `self` IS narrowed (`block_as_methods:`, `define_method`, a `Class.new` body) has cleared the
848
+ # mark and keeps being checked, unless the narrowing was read off an unknown enclosing `self`.
849
+ def unknown_block_self_receiver?(call_node, scope)
850
+ call_node.receiver.is_a?(Prism::SelfNode) && scope.block_self_unknown?
786
851
  end
787
852
 
788
853
  # Issue #1120 — the method exists at THIS call site but not everywhere: a refinement whose `using` is in
789
854
  # effect here, or a `def o.m` on the receiver local in this scope. Asked from {#last_resort_surface_answers?},
790
855
  # once every project-wide table has come back "absent", so the file walk behind {LexicalMethodSites} runs
791
856
  # only for a call about to fire.
792
- # A refinement answers instance-side receivers only: a refined singleton (`refine X.singleton_class`) names
793
- # no constant target, so nothing records it.
794
857
  def lexically_defined_method?(class_name, call_node, scope, kind, lexical_sites)
795
858
  return false if lexical_sites.nil?
796
859
  return true if lexical_sites.singleton_local_def?(call_node)
797
- return false unless kind == :instance
798
860
 
799
- # ADR-46 — the answer below is a function of every refinement of this name in the project, so the
800
- # consumer depends on the name whichever way it answers; a refine body edited in another file must
801
- # re-check it (`IncrementalSession#refinement_affected`).
802
- DependencyRecorder.read_name(:refinement, call_node.name) if DependencyRecorder.active?
803
- modules = refining_modules(scope, class_name, call_node.name)
804
- !modules.nil? && lexical_sites.refinement_active?(call_node, modules)
861
+ refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites)
805
862
  end
806
863
 
807
- # Issue #1120 — every module that refines `method_name` into `class_name` or one of its ancestors (a
808
- # refinement of `Object` reaches a `String` receiver), or nil when none does. The table is empty on a
809
- # project that refines nothing, which answers without a lookup.
810
- def refining_modules(scope, class_name, method_name)
811
- refinements = scope.discovered_refinements
812
- return nil if refinements.empty?
864
+ # Is a refinement of `call_node`'s method name into `class_name` (or an ancestor) in effect at the call?
865
+ # Issue #1120 asks it for a method the class lacks. The two argument rules ask
866
+ # {#refined_redefinition_in_effect?} instead (issue #1740), and this only for a class-object receiver.
867
+ # Typing the call from the refine body is #1664.
868
+ # A refinement answers instance-side receivers only: a refined singleton (`refine X.singleton_class`) names
869
+ # no constant target, so nothing records it. Issue #1673 — the answer is derived from the file's in-effect
870
+ # refinements ({Inference::InEffectRefinements}), the list the typer reads, with each `using` expanded
871
+ # through its module's includes (issue #1671).
872
+ #
873
+ # ADR-121 WD7 — a module in effect whose refinements Rigor cannot read
874
+ # ({Inference::InEffectRefinements.opaque_module?}: a gem's, one from outside the analysed paths, one with an
875
+ # unreadable `refine`) may refine any class, a class object's included, so the call declines whatever its
876
+ # receiver. Issue #1796; both argument rules ask this predicate too.
877
+ def refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites)
878
+ return false if lexical_sites.nil?
879
+ return true if opaque_refinement_in_effect?(call_node, scope, lexical_sites)
813
880
 
814
- modules = nil
815
- refinements.each do |refined, methods|
816
- names = methods[method_name]
817
- next if names.nil? || !refined_receiver_class?(scope, class_name, refined)
881
+ modules =
882
+ if kind == :instance
883
+ Inference::InEffectRefinements.refining_modules(scope, class_name, call_node.name)
884
+ else
885
+ # ADR-121 WD7 — a row whose class the walk could not name (`refine(k)` with `k` a singleton class)
886
+ # reaches a class object too.
887
+ Inference::InEffectRefinements.class_unknown_refining_modules(scope, call_node.name)
888
+ end
889
+ !modules.nil? &&
890
+ lexical_sites.refinements.refinement_active?(call_node.location.start_offset, modules,
891
+ scope.declared_refinements) do |name|
892
+ Inference::InEffectRefinements.activated_modules(scope, name)
893
+ end
894
+ end
818
895
 
819
- (modules ||= []).concat(names)
896
+ # Issues #1663, #1740 — the decline `call.wrong-arity` and `call.argument-type-mismatch` ask once a report
897
+ # would fire: a refinement that REDEFINES a method the class already has replaces the signature those rules
898
+ # check against, but Ruby finds a method the receiver's class (or a nearer ancestor) defines before it
899
+ # consults an ancestor's refinement (`refine Object do def center(a, b, c)` leaves `"x".center(1, 2, 3)` an
900
+ # `ArgumentError`). It stands on its own rather than behind {#refined_method_in_effect?}, whose ancestor
901
+ # reach declines `class Inc; def bar(a); include N; end` under `refine N do def bar(a, b, c)` (issue #1797).
902
+ #
903
+ # A call with nothing in effect is checked. Otherwise it reports only from positive knowledge: the list
904
+ # carries no unknown marker and no opaque module, and {Inference::RefinedDispatch::OwnMethod} proves the
905
+ # receiver's own definer answers first. A class-object receiver keeps {#refined_method_in_effect?}'s
906
+ # answer: its singleton-side levels are not walked yet.
907
+ def refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
908
+ return false if lexical_sites.nil?
909
+
910
+ list = refinement_list_at(call_node, scope, lexical_sites)
911
+ return false if list.empty?
912
+ return true if list.include?(Inference::InEffectRefinements::UNKNOWN)
913
+ return true if opaque_refinement_in_effect?(call_node, scope, lexical_sites)
914
+ return refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites) unless kind == :instance
915
+
916
+ !Inference::RefinedDispatch::OwnMethod.answers?(scope, class_name, call_node.name, list)
917
+ end
918
+
919
+ # The in-effect list at the call as the typer reads it: the lexical activations, each `using` expanded,
920
+ # then the plugin-declared modules (#1667).
921
+ def refinement_list_at(call_node, scope, lexical_sites)
922
+ query = lexical_sites.refinements
923
+ declared = scope.declared_refinements
924
+ return Inference::InEffectRefinements::EMPTY if query.empty? && declared.empty?
925
+
926
+ query.at(call_node.location.start_offset, declared) do |name|
927
+ Inference::InEffectRefinements.activated_modules(scope, name)
820
928
  end
821
- modules
822
929
  end
823
930
 
824
- def refined_receiver_class?(scope, class_name, refined)
825
- return true if class_name == refined
931
+ # ADR-121 WD7 — does the call's in-effect list (its lexical activations, each `using` expanded; a
932
+ # plugin-declared module is the plugin's declaration and never opaque) hold an opaque module?
933
+ def opaque_refinement_in_effect?(call_node, scope, lexical_sites)
934
+ query = lexical_sites.refinements
935
+ return false if query.empty?
826
936
 
827
- environment = scope.environment
828
- !environment.nil? && environment.class_ordering(class_name, refined) == :subclass
937
+ list = Inference::InEffectRefinements.lexical_list(scope, query, call_node.location.start_offset)
938
+ Inference::InEffectRefinements.opaque_in?(scope, list)
829
939
  end
830
940
 
831
941
  # The probes that run only once every cheaper answer has come back "absent", kept together
@@ -858,10 +968,25 @@ module Rigor
858
968
  return true if module_mixin_receiver?(receiver_type, scope)
859
969
  return true if mixin_self_class_receiver?(call_node, scope)
860
970
  return true if unknown_mixin_includer?(class_name, scope)
971
+ return true if object_mixin_may_define?(receiver_type, class_name, call_node.name, scope)
861
972
 
862
973
  ancestry_declares_method?(scope, class_name, call_node.name, kind)
863
974
  end
864
975
 
976
+ # Issue #1697 — a module the project mixes into `Object` (a top-level `include M`, or `include M` in
977
+ # `class Object`) gives every object its instance methods, and the RBS lookup above only saw core's
978
+ # `Object`. A class object is an `Object` whatever class it is, so the singleton side always reaches
979
+ # it; the instance side does when the receiver's class descends from `Object`. A mixed-in module
980
+ # whose surface is unknown, or a `module_function` reached only privately, does not count here
981
+ # ({Inference::ObjectMixins.may_define?}).
982
+ def object_mixin_may_define?(receiver_type, class_name, method_name, scope)
983
+ unless receiver_type.is_a?(Type::Singleton) || Inference::ObjectMixins.reaches_object?(scope, class_name)
984
+ return false
985
+ end
986
+
987
+ Inference::ObjectMixins.may_define?(scope, method_name, receiver: :explicit)
988
+ end
989
+
865
990
  # Issue #739 — the singleton twin of the module-mixin receiver, and the only shape that reaches the
866
991
  # singleton side legitimately. `self.class` inside a mixin's instance method is the INCLUDER's class
867
992
  # at runtime; the engine types it `Singleton[<the module>]`, one hop along from the same wrong
@@ -999,6 +1124,15 @@ module Rigor
999
1124
  # 3. The standard `Kernel` / `Object` private-method
1000
1125
  # surface (`puts`, `p`, `require`, `loop`, `raise`,
1001
1126
  # …) drawn from the loaded RBS environment.
1127
+ # 4. The private singleton methods of the top-level `main`
1128
+ # object (`using`, `include`, `public`, `private`,
1129
+ # `define_method`), read from RBS core's
1130
+ # `RBS::Unnamed::TopLevelSelfClass`, plus
1131
+ # `ruby2_keywords` (issue #1383).
1132
+ # 5. The instance methods of a module the project mixes
1133
+ # into `Object` — a top-level `include M`, or an
1134
+ # `include M` in `class Object` — or any call at all
1135
+ # when such a module's surface is unknown (issue #1697).
1002
1136
  #
1003
1137
  # The rule deliberately does NOT generalise to
1004
1138
  # implicit-self calls inside `def` / `class` / `module`
@@ -1010,7 +1144,7 @@ module Rigor
1010
1144
  # Authored severity is `:warning`; the severity profile
1011
1145
  # remaps it (`strict` → `:error`, `balanced` →
1012
1146
  # `:warning`, `lenient` → `:off` / suppressed).
1013
- def unresolved_toplevel_diagnostic(path, call_node, scope_index, eval_ranges = nil)
1147
+ def unresolved_toplevel_diagnostic(path, call_node, scope_index, eval_ranges = nil, lexical_sites = nil)
1014
1148
  return nil unless call_node.receiver.nil?
1015
1149
 
1016
1150
  scope = scope_index[call_node]
@@ -1021,15 +1155,38 @@ module Rigor
1021
1155
  return nil if scope.top_level_def_for(name)
1022
1156
  return nil if source_declared_method?(scope, "Object", name, :instance)
1023
1157
  return nil if Rigor::Reflection.instance_method_definition("Object", name, scope: scope)
1158
+ return nil if main_singleton_method?(name, scope)
1159
+ return nil if Inference::ObjectMixins.may_define?(scope, name, receiver: :implicit)
1024
1160
  # `Target.class_eval { def added = 1; def use_added = added }` files the defs on Target
1025
1161
  # but leaves the eval body with a nil `self_type`, so `toplevel?` still holds. An eval
1026
1162
  # body is morally a class body, so ADR-34 stays silent there — including on a genuinely
1027
1163
  # undefined name inside the block. Ranges are computed once per file.
1028
1164
  return nil if call_inside_receiver_eval_ranges?(eval_ranges, call_node)
1165
+ # ADR-121 WD7 — a refinement of `Object` (or one Rigor cannot read) in effect here may define the name.
1166
+ return nil if refined_method_in_effect?("Object", call_node, scope, :instance, lexical_sites)
1029
1167
 
1030
1168
  build_unresolved_toplevel_diagnostic(path, call_node)
1031
1169
  end
1032
1170
 
1171
+ # Issue #1383 — RBS core declares `main`'s private singleton methods on
1172
+ # `RBS::Unnamed::TopLevelSelfClass`, so the set follows RBS rather than a hand-kept
1173
+ # list. CRuby 4.0's `main` also has `ruby2_keywords`, which RBS does not declare.
1174
+ # rbs 3.x has no `TopLevelSelfClass`, so there only `ruby2_keywords` is covered.
1175
+ # A call inside a top-level block (`describe do include M end`) is judged here too;
1176
+ # silencing it is the cheaper error.
1177
+ MAIN_SINGLETON_CLASS_NAME = "RBS::Unnamed::TopLevelSelfClass"
1178
+ MAIN_SINGLETON_METHODS_MISSING_FROM_RBS = %i[ruby2_keywords].freeze
1179
+ private_constant :MAIN_SINGLETON_CLASS_NAME, :MAIN_SINGLETON_METHODS_MISSING_FROM_RBS
1180
+
1181
+ def main_singleton_method?(name, scope)
1182
+ return true if MAIN_SINGLETON_METHODS_MISSING_FROM_RBS.include?(name)
1183
+ # `TopLevelSelfClass < Object`: when the project's own RBS broke `Object`, building it would only
1184
+ # fail again and report a second, unfixable class name beside `Object` (#696's report).
1185
+ return false if Rigor::Reflection.instance_definition("Object", scope: scope).nil?
1186
+
1187
+ !Rigor::Reflection.instance_method_definition(MAIN_SINGLETON_CLASS_NAME, name, scope: scope).nil?
1188
+ end
1189
+
1033
1190
  # ScopeIndexer keeps RECEIVER_EVAL_CALLS private; this list is the same family
1034
1191
  # (`class_eval` / `module_eval` / `class_exec` / `module_exec` / `instance_eval` /
1035
1192
  # `instance_exec`). Used only to decline ADR-34 inside those blocks.
@@ -1305,8 +1462,11 @@ module Rigor
1305
1462
  # - ADR-67 WD6b — an inferred-parameter receiver's type is an open-call-site lower bound, so firing
1306
1463
  # undefined-method against it is a false positive by construction.
1307
1464
  # - Issue #653 — a plugin answered this call site ({#plugin_typed_call?}).
1465
+ # - Issue #1717 — an explicit `self` receiver in a block whose `self` is unmodelled
1466
+ # ({#unknown_block_self_receiver?}).
1308
1467
  def call_site_exempt?(call_node, scope)
1309
- inferred_param_receiver?(call_node, scope) || plugin_typed_call?(call_node, scope)
1468
+ inferred_param_receiver?(call_node, scope) || plugin_typed_call?(call_node, scope) ||
1469
+ unknown_block_self_receiver?(call_node, scope)
1310
1470
  end
1311
1471
 
1312
1472
  # Issue #653 — true when a plugin's `dynamic_return` supplied the return type for THIS call node
@@ -1568,7 +1728,7 @@ module Rigor
1568
1728
  # when the call's receiver / RBS coverage / call shape
1569
1729
  # disqualifies the rule.
1570
1730
  # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
1571
- def wrong_arity_diagnostic(path, call_node, scope_index)
1731
+ def wrong_arity_diagnostic(path, call_node, scope_index, lexical_sites = nil)
1572
1732
  return nil if call_node.receiver.nil?
1573
1733
  return nil unless plain_positional_call?(call_node)
1574
1734
 
@@ -1609,11 +1769,18 @@ module Rigor
1609
1769
  source_arity&.settle_by_definitions
1610
1770
  return nil
1611
1771
  end
1772
+ return nil if arity_report_withheld?(source_arity, class_name, call_node, scope, kind, lexical_sites)
1773
+
1774
+ build_arity_diagnostic(path, call_node, class_name, min, max, actual)
1775
+ end
1776
+
1777
+ # The declines `call.wrong-arity` asks only once the arity is out of the envelope.
1778
+ def arity_report_withheld?(source_arity, class_name, call_node, scope, kind, lexical_sites)
1612
1779
  # Issue #992 — a source envelope's remaining declines can only withhold, so they run only now.
1613
1780
  source_arity&.settle_by_walk
1614
- return nil if source_arity && !source_arity.authoritative?(class_name)
1781
+ return true if source_arity && !source_arity.authoritative?(class_name)
1615
1782
 
1616
- build_arity_diagnostic(path, call_node, class_name, min, max, actual)
1783
+ refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
1617
1784
  end
1618
1785
 
1619
1786
  # The `[min, max]` the call is checked against — a declared signature's, or (issue #992) the project
@@ -1813,7 +1980,7 @@ module Rigor
1813
1980
  # and union receivers where every member already
1814
1981
  # disqualifies the call (avoid duplicating the
1815
1982
  # undefined-method diagnostic).
1816
- def nil_receiver_diagnostic(path, call_node, scope_index)
1983
+ def nil_receiver_diagnostic(path, call_node, scope_index, lexical_sites = nil)
1817
1984
  return nil if call_node.receiver.nil?
1818
1985
  # Safe-navigation calls (`recv&.method`) already
1819
1986
  # short-circuit on nil at runtime, so a nil-bearing
@@ -1862,10 +2029,22 @@ module Rigor
1862
2029
  return nil unless Rigor::Reflection.rbs_class_known?("NilClass", scope: scope)
1863
2030
 
1864
2031
  return nil unless nil_bearing_union_witnesses?(receiver_type, call_node.name, scope)
2032
+ return nil if nil_receiver_withheld?(call_node, receiver_type, scope, scope_index, lexical_sites)
1865
2033
 
1866
2034
  build_nil_receiver_diagnostic(path, call_node)
1867
2035
  end
1868
2036
 
2037
+ # The declines `call.possible-nil-receiver` asks only once a report would fire.
2038
+ def nil_receiver_withheld?(call_node, receiver_type, scope, scope_index, lexical_sites)
2039
+ # Issue #1703 — a `nil` that only a `key?` guard on the same receiver and key rules out. It re-walks the file
2040
+ # once with guards on, and only to withhold this report.
2041
+ return true if Inference::KeyPresenceGuard.withholds_nil?(call_node, receiver_type, lexical_sites&.root,
2042
+ scope_index)
2043
+
2044
+ # ADR-121 WD7 — a refinement of `NilClass` (or one Rigor cannot read) in effect may define the name.
2045
+ refined_method_in_effect?("NilClass", call_node, scope, :instance, lexical_sites)
2046
+ end
2047
+
1869
2048
  # The receiver-type half of the rule, factored out of the node-shape
1870
2049
  # guards above: the union must carry nil, must carry a non-nil arm the
1871
2050
  # presence question can be asked of, must support the method on every
@@ -1957,7 +2136,9 @@ module Rigor
1957
2136
 
1958
2137
  # Issue #723 — the arm's own RBS does not have it; its project ancestry still might. Last, for
1959
2138
  # the reason {#ancestry_declares_method?} carries: this is the probe that walks the class graph.
1960
- ancestry_declares_method?(scope, class_name, method_name, :instance)
2139
+ return true if ancestry_declares_method?(scope, class_name, method_name, :instance)
2140
+
2141
+ object_mixin_may_define?(member, class_name, method_name, scope)
1961
2142
  end
1962
2143
 
1963
2144
  def nil_class_has_method?(method_name, scope)
@@ -1984,7 +2165,7 @@ module Rigor
1984
2165
  # `possible-nil-receiver` rule, safe-navigation, and ADR-58
1985
2166
  # declaration-sourced nil. Slice 1 handles pure non-nil unions
1986
2167
  # (e.g. `String | Symbol`).
1987
- def union_undefined_method_diagnostic(path, call_node, receiver_type, scope)
2168
+ def union_undefined_method_diagnostic(path, call_node, receiver_type, scope, lexical_sites = nil)
1988
2169
  return nil unless receiver_type.is_a?(Type::Union)
1989
2170
  return nil if call_node.safe_navigation?
1990
2171
 
@@ -2006,10 +2187,44 @@ module Rigor
2006
2187
  # `.pack`). Require at least two distinct arm classes.
2007
2188
  return nil if members.map { |member| concrete_class_name(member) }.uniq.size < 2
2008
2189
  return nil if members.any? { |member| method_present_anywhere?(member, call_node.name, scope) }
2190
+ return nil if refined_on_some_arm?(members, call_node, scope, lexical_sites)
2009
2191
 
2010
2192
  build_undefined_method_diagnostic(path, call_node, receiver_type)
2011
2193
  end
2012
2194
 
2195
+ # ADR-121 WD7 — a refinement in effect (or one Rigor cannot read) may define the name on one arm's class.
2196
+ def refined_on_some_arm?(members, call_node, scope, lexical_sites)
2197
+ members.any? do |member|
2198
+ refined_method_in_effect?(concrete_class_name(member), call_node, scope, :instance, lexical_sites)
2199
+ end
2200
+ end
2201
+
2202
+ # Issue #1699 — a union of one class's literals, alone or beside that class's plain nominal
2203
+ # (`0 | Integer`, which `gets.to_i` types; `"a" | "b"`; `1 | 2`), holds nothing but instances of
2204
+ # that class, so it answers a method-existence question exactly as the class does. The union rule
2205
+ # above declines it through its distinct-class guard, which exists for SHAPE joins
2206
+ # (`Hash[K1, V1] | Hash[K2, V2]`, a corpus misinference on mail), and the scalar rule declined it
2207
+ # for having no single concrete class, so neither judged it. Returns that class's nominal, or `type`
2208
+ # itself. Literal-and-plain-nominal arms only: a generic, shape or refinement arm keeps the union
2209
+ # declined, and so does a union spanning two classes.
2210
+ def single_class_literal_union(type)
2211
+ return type unless type.is_a?(Type::Union)
2212
+
2213
+ members = type.members
2214
+ return type unless members.any?(Type::Constant)
2215
+ return type unless members.all? do |member|
2216
+ member.is_a?(Type::Constant) || (member.is_a?(Type::Nominal) && member.type_args.empty?)
2217
+ end
2218
+
2219
+ class_names = members.map { |member| concrete_class_name(member) }.uniq
2220
+ return type unless class_names.size == 1
2221
+
2222
+ class_name = class_names.first
2223
+ return type if class_name.nil? || class_name == "NilClass"
2224
+
2225
+ Type::Combinator.nominal_of(class_name)
2226
+ end
2227
+
2013
2228
  # An arm that makes a sound "undefined on every arm" verdict impossible: a non-class surface
2014
2229
  # (Dynamic / Top / Bot), a singleton (slice 1 reasons about instance arms only), the generic
2015
2230
  # metaclass `Class` / `Module` (a value typed as one is *some* class/module object whose singleton
@@ -2150,9 +2365,13 @@ module Rigor
2150
2365
  INTEGER_RAISING_OPERATORS = %i[/ % div modulo divmod].freeze
2151
2366
  private_constant :INTEGER_RAISING_OPERATORS
2152
2367
 
2153
- def always_raises_diagnostic(path, call_node, scope_index)
2368
+ def always_raises_diagnostic(path, call_node, scope_index, lexical_sites = nil)
2154
2369
  return nil unless integer_zero_division?(call_node, scope_index)
2155
2370
 
2371
+ # ADR-121 WD7 — a refinement of `Integer`'s operator (or one Rigor cannot read) in effect may not raise.
2372
+ scope = scope_index[call_node]
2373
+ return nil if refined_method_in_effect?("Integer", call_node, scope, :instance, lexical_sites)
2374
+
2156
2375
  build_always_raises_diagnostic(path, call_node)
2157
2376
  end
2158
2377
 
@@ -2493,7 +2712,7 @@ module Rigor
2493
2712
  names = [method_name, *GLOBAL_WRITE_HATCHES]
2494
2713
  census = global_write_census(scope)
2495
2714
  program_may_answer?(ancestors, names, census, scope) ||
2496
- (method_name == :write && refined_writer_in_effect?(census, node, lexical_sites))
2715
+ (method_name == :write && refined_writer_in_effect?(census, node, scope, lexical_sites))
2497
2716
  end
2498
2717
 
2499
2718
  def overridden_hatch?(definition)
@@ -2549,9 +2768,10 @@ module Rigor
2549
2768
  # refine(T)`), so a refined `write` on any class declines every literal. `respond_to?(:write)` honours an
2550
2769
  # active refinement on Ruby 4.0.5; a refined `respond_to_missing?` or `respond_to?`, and every implicit
2551
2770
  # conversion, do not, so a refinement that adds only those leaves the write reported.
2552
- def refined_writer_in_effect?(census, node, lexical_sites)
2771
+ def refined_writer_in_effect?(census, node, scope, lexical_sites)
2553
2772
  Inference::GlobalWriteCensus.refines_write?(census) &&
2554
- (lexical_sites.nil? || lexical_sites.using_in_effect?(node))
2773
+ (lexical_sites.nil? || !scope.declared_refinements.empty? ||
2774
+ lexical_sites.refinements.any_at?(node.location.start_offset))
2555
2775
  end
2556
2776
 
2557
2777
  def build_global_write_type_diagnostic(path, node, contract, class_name)
@@ -2634,7 +2854,7 @@ module Rigor
2634
2854
  # - Issue #1568 — a module the class PREPENDS sits ahead of it in Ruby's order, so a definition there
2635
2855
  # is what the call reaches; the class's own private `def` answers only when nothing prepended
2636
2856
  # defines the name ({#reached_definer_visibility}).
2637
- def visibility_mismatch_diagnostic(path, call_node, scope_index)
2857
+ def visibility_mismatch_diagnostic(path, call_node, scope_index, lexical_sites = nil)
2638
2858
  return nil unless explicit_non_self_receiver?(call_node.receiver)
2639
2859
 
2640
2860
  scope = scope_index[call_node]
@@ -2653,6 +2873,8 @@ module Rigor
2653
2873
  reached = reached_definer_visibility(scope, receiver_type.class_name, call_node.name)
2654
2874
  return nil if reached == :undecided
2655
2875
  return nil unless reached.nil? || reached == :private
2876
+ # ADR-121 WD7 — a refinement in effect (or one Rigor cannot read) may redefine the method, public.
2877
+ return nil if refined_method_in_effect?(receiver_type.class_name, call_node, scope, :instance, lexical_sites)
2656
2878
 
2657
2879
  build_visibility_mismatch_diagnostic(path, call_node, receiver_type)
2658
2880
  end
@@ -2905,7 +3127,7 @@ module Rigor
2905
3127
  COERCE_DISPATCH_METHODS = %i[+ - * / % ** & | ^ << >> < > <= >=].to_set.freeze
2906
3128
  private_constant :COERCE_DISPATCH_METHODS
2907
3129
 
2908
- def argument_type_diagnostic(path, call_node, scope_index)
3130
+ def argument_type_diagnostic(path, call_node, scope_index, lexical_sites = nil)
2909
3131
  return nil if call_node.receiver.nil?
2910
3132
  return nil if UNIVERSAL_EQUALITY_METHODS.include?(call_node.name)
2911
3133
  return nil unless plain_positional_call?(call_node)
@@ -2932,11 +3154,19 @@ module Rigor
2932
3154
  param_overrides = Rigor::RbsExtended.param_type_override_map(method_def, environment: scope.environment)
2933
3155
  mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, scope_index)
2934
3156
  return nil if mismatch.nil?
2935
- return nil if inferred_param_mismatch_verdict?(call_node, mismatch, scope)
3157
+ return nil if argument_report_withheld?(receiver_type, class_name, call_node, mismatch, scope, lexical_sites)
2936
3158
 
2937
3159
  build_argument_type_diagnostic(path, call_node, class_name, mismatch)
2938
3160
  end
2939
3161
 
3162
+ # The declines `call.argument-type-mismatch` asks only once an argument mismatches.
3163
+ def argument_report_withheld?(receiver_type, class_name, call_node, mismatch, scope, lexical_sites)
3164
+ return true if inferred_param_mismatch_verdict?(call_node, mismatch, scope)
3165
+
3166
+ kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
3167
+ refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
3168
+ end
3169
+
2940
3170
  # ADR-67 WD6b — an argument-type-mismatch verdict resting on an open-call-site lower bound, on
2941
3171
  # either side of the call. The ARGUMENT side: the mismatching argument is an inferred-parameter
2942
3172
  # local, so firing against it is an FP by construction. The RECEIVER side: when the receiver roots
@@ -3738,6 +3968,7 @@ module Rigor
3738
3968
  # silent.
3739
3969
  def override_visibility_diagnostic(path, def_node, scope_index)
3740
3970
  return nil unless def_node.receiver.nil? # instance methods only
3971
+ return nil if IMPLICITLY_PRIVATE_METHODS.include?(def_node.name)
3741
3972
 
3742
3973
  scope = scope_index[def_node]
3743
3974
  return nil if scope.nil?
@@ -3869,6 +4100,7 @@ module Rigor
3869
4100
  # fire) when any gate is unmet.
3870
4101
  def resolve_authored_override(def_node, scope_index)
3871
4102
  return nil unless def_node.receiver.nil? # instance methods only (singleton: follow-on)
4103
+ return nil if LIFECYCLE_HOOKS.include?(def_node.name) # ADR-35 WD10: outside substitutability
3872
4104
 
3873
4105
  scope = scope_index[def_node]
3874
4106
  return nil if scope.nil?