rigortype 0.3.6 → 0.3.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 (223) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/builtins/ruby_core/date.yml +6 -3
  4. data/data/builtins/ruby_core/enumerable.yml +1 -0
  5. data/data/builtins/ruby_core/exception.yml +2 -1
  6. data/data/builtins/ruby_core/file.yml +28 -14
  7. data/data/builtins/ruby_core/hash.yml +5 -2
  8. data/data/builtins/ruby_core/io.yml +33 -16
  9. data/data/builtins/ruby_core/random.yml +2 -1
  10. data/data/builtins/ruby_core/re.yml +6 -3
  11. data/data/builtins/ruby_core/struct.yml +2 -0
  12. data/data/builtins/ruby_core/time.yml +2 -1
  13. data/data/gem_overlay/activesupport/core_ext.rbs +409 -11
  14. data/docs/handbook/11-sig-gen.md +24 -14
  15. data/docs/manual/02-cli-reference.md +23 -7
  16. data/docs/manual/04-diagnostics.md +1 -1
  17. data/docs/manual/05-inspecting-types.md +20 -8
  18. data/docs/manual/07-plugins.md +4 -0
  19. data/docs/manual/plugins/rigor-actionpack.md +61 -0
  20. data/docs/manual/plugins/rigor-activerecord.md +74 -1
  21. data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
  22. data/docs/manual/plugins/rigor-sidekiq.md +22 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
  24. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
  25. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
  27. data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
  28. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
  29. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
  30. data/lib/rigor/analysis/check_rules.rb +383 -48
  31. data/lib/rigor/analysis/crash_signature.rb +188 -0
  32. data/lib/rigor/analysis/dependency_recorder.rb +18 -3
  33. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  34. data/lib/rigor/analysis/incremental.rb +28 -0
  35. data/lib/rigor/analysis/incremental_session.rb +63 -16
  36. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
  37. data/lib/rigor/analysis/reachability/graph.rb +14 -5
  38. data/lib/rigor/analysis/reachability/scan.rb +7 -3
  39. data/lib/rigor/analysis/result.rb +32 -0
  40. data/lib/rigor/analysis/run_cache_key.rb +41 -1
  41. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +233 -16
  42. data/lib/rigor/analysis/runner/pool_coordinator.rb +266 -47
  43. data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
  44. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -2
  45. data/lib/rigor/analysis/runner.rb +212 -16
  46. data/lib/rigor/analysis/worker_session.rb +37 -3
  47. data/lib/rigor/bleeding_edge.rb +7 -6
  48. data/lib/rigor/builtins/hkt_builtins.rb +45 -6
  49. data/lib/rigor/builtins/imported_refinements.rb +11 -9
  50. data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
  51. data/lib/rigor/builtins/static_return_refinements.rb +20 -1
  52. data/lib/rigor/cache/annotation_location.rb +72 -0
  53. data/lib/rigor/cache/descriptor.rb +42 -2
  54. data/lib/rigor/cache/incremental_snapshot.rb +40 -3
  55. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +70 -8
  56. data/lib/rigor/cache/store.rb +17 -1
  57. data/lib/rigor/cli/coverage_command.rb +23 -18
  58. data/lib/rigor/cli/coverage_mutation.rb +13 -17
  59. data/lib/rigor/cli/coverage_scan.rb +47 -6
  60. data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
  61. data/lib/rigor/cli/fused_protection_report.rb +23 -2
  62. data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
  63. data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
  64. data/lib/rigor/cli/mutation_protection_report.rb +31 -2
  65. data/lib/rigor/cli/sig_gen_command.rb +47 -0
  66. data/lib/rigor/cli/type_of_command.rb +188 -41
  67. data/lib/rigor/cli/type_of_renderer.rb +72 -3
  68. data/lib/rigor/cli/type_scan_command.rb +10 -1
  69. data/lib/rigor/cli.rb +1 -1
  70. data/lib/rigor/configuration.rb +7 -4
  71. data/lib/rigor/effects/plugin_facts.rb +1 -1
  72. data/lib/rigor/effects/scanner.rb +2 -3
  73. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
  74. data/lib/rigor/environment/failure_slot.rb +28 -0
  75. data/lib/rigor/environment/lockfile_resolver.rb +15 -5
  76. data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
  77. data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
  78. data/lib/rigor/environment/rbs_loader.rb +743 -93
  79. data/lib/rigor/environment.rb +125 -25
  80. data/lib/rigor/inference/acceptance.rb +172 -20
  81. data/lib/rigor/inference/captured_locals.rb +62 -0
  82. data/lib/rigor/inference/content_join.rb +347 -0
  83. data/lib/rigor/inference/def_handle.rb +15 -3
  84. data/lib/rigor/inference/def_node_resolver.rb +62 -3
  85. data/lib/rigor/inference/expression_typer.rb +1020 -141
  86. data/lib/rigor/inference/fork_map.rb +6 -1
  87. data/lib/rigor/inference/hkt_reducer.rb +12 -1
  88. data/lib/rigor/inference/hkt_registry.rb +61 -3
  89. data/lib/rigor/inference/hkt_sugar_translator.rb +87 -0
  90. data/lib/rigor/inference/index_write_widening.rb +48 -0
  91. data/lib/rigor/inference/indexed_narrowing.rb +14 -0
  92. data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
  93. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
  94. data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
  95. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
  96. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
  97. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
  98. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
  99. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
  100. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
  101. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
  102. data/lib/rigor/inference/method_dispatcher.rb +155 -14
  103. data/lib/rigor/inference/method_parameter_binder.rb +3 -3
  104. data/lib/rigor/inference/mutation_widening.rb +244 -174
  105. data/lib/rigor/inference/narrowing.rb +331 -27
  106. data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
  107. data/lib/rigor/inference/pre_eval_constants.rb +15 -0
  108. data/lib/rigor/inference/precision_scanner.rb +5 -2
  109. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  110. data/lib/rigor/inference/rbs_type_translator.rb +89 -39
  111. data/lib/rigor/inference/scope_indexer.rb +1493 -140
  112. data/lib/rigor/inference/statement_evaluator.rb +276 -91
  113. data/lib/rigor/inference/struct_fold_safety.rb +186 -27
  114. data/lib/rigor/inference/version_guard.rb +229 -0
  115. data/lib/rigor/language_server/selection_range_provider.rb +1 -1
  116. data/lib/rigor/plugin/base.rb +3 -2
  117. data/lib/rigor/plugin/inflector.rb +14 -5
  118. data/lib/rigor/plugin/io_boundary.rb +107 -4
  119. data/lib/rigor/plugin/loader.rb +14 -0
  120. data/lib/rigor/plugin/registry.rb +25 -2
  121. data/lib/rigor/protection/analysis_guard.rb +60 -0
  122. data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
  123. data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
  124. data/lib/rigor/protection/discovery_seed.rb +1 -1
  125. data/lib/rigor/protection/measurement_integrity.rb +36 -0
  126. data/lib/rigor/protection/mutation_scanner.rb +61 -6
  127. data/lib/rigor/protection/mutator.rb +121 -18
  128. data/lib/rigor/rbs_extended/envelope_scanner.rb +8 -7
  129. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  130. data/lib/rigor/rbs_extended/reporter.rb +93 -13
  131. data/lib/rigor/rbs_extended.rb +6 -1
  132. data/lib/rigor/reflection.rb +182 -30
  133. data/lib/rigor/scope/discovery_index.rb +39 -1
  134. data/lib/rigor/scope.rb +385 -4
  135. data/lib/rigor/sig_gen/classification.rb +12 -1
  136. data/lib/rigor/sig_gen/generator.rb +320 -85
  137. data/lib/rigor/sig_gen/renderer.rb +14 -12
  138. data/lib/rigor/sig_gen/writer.rb +3 -0
  139. data/lib/rigor/source/constant_path.rb +79 -2
  140. data/lib/rigor/triage/catalogue.rb +1 -1
  141. data/lib/rigor/type/combinator.rb +10 -0
  142. data/lib/rigor/type/maybe.rb +47 -0
  143. data/lib/rigor/type/refined.rb +1 -2
  144. data/lib/rigor/type/result.rb +53 -0
  145. data/lib/rigor/type.rb +2 -0
  146. data/lib/rigor/version.rb +1 -1
  147. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
  148. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
  149. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
  150. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
  151. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
  152. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
  153. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
  154. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
  155. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
  156. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
  157. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
  158. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
  159. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
  160. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
  161. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
  162. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
  163. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
  164. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
  165. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
  166. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
  167. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
  168. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
  169. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
  170. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
  171. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
  172. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
  173. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
  174. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
  175. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
  176. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
  177. data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
  178. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
  179. data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
  180. data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
  181. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
  182. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
  183. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
  184. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
  185. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
  186. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
  187. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
  188. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
  189. data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
  190. data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
  191. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
  192. data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
  193. data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
  194. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
  195. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
  196. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  197. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
  198. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
  199. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
  200. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
  201. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
  202. data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
  203. data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
  204. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
  205. data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
  206. data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
  207. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
  208. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
  209. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
  210. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
  211. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
  212. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
  213. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
  214. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
  215. data/sig/rigor/analysis/baseline.rbs +1 -1
  216. data/sig/rigor/environment.rbs +9 -0
  217. data/sig/rigor/inference.rbs +4 -1
  218. data/sig/rigor/plugin/io_boundary.rbs +2 -0
  219. data/sig/rigor/reflection.rbs +3 -1
  220. data/sig/rigor/scope.rbs +25 -1
  221. data/sig/rigor/type.rbs +95 -1
  222. data/sig/rigor.rbs +16 -0
  223. metadata +71 -3
@@ -42,11 +42,16 @@ module Rigor
42
42
  #
43
43
  # A child ends at {.run_worker}'s `exit!`, which skips `at_exit` AND object finalizers, so a `Tempfile`
44
44
  # a child creates is never reclaimed by the mechanism that reclaims the parent's (issue #330 —
45
- # `Protection::ClosureKillOracle`'s per-process mutant file leaked one file per worker per
45
+ # `Protection::ClosureKillOracle`'s mutant file leaked one file per worker per
46
46
  # `rigor coverage --protection --mutation` run). Answering with the directory the parent already removes
47
47
  # once every child has been collected makes the child's scratch state the parent's problem, which is the
48
48
  # only place it can be solved: the names are random, so nothing outside the child could find them.
49
49
  #
50
+ # Still load-bearing after issue #572 gave that oracle a block-scoped temp file. The block unwinds on
51
+ # both paths a child can take on its own — a normal return and the `StandardError` {.run_worker}
52
+ # rescues — but a child killed by a signal unwinds nothing at all, and this directory is what reclaims
53
+ # what it left behind.
54
+ #
50
55
  # The directory check is the belt to {.run_worker}'s braces: the offer is only worth taking while the
51
56
  # parent still has the directory open, and answering `nil` once it is gone degrades to `Dir.tmpdir`
52
57
  # instead of raising `Errno::ENOENT` somewhere far from here.
@@ -85,7 +85,7 @@ module Rigor
85
85
  Type::Combinator.union(*reduced)
86
86
  when HktBody::NominalApp
87
87
  reduced_args = node.args.map { |arg| walk(arg, bindings: bindings, state: state) }
88
- Type::Combinator.nominal_of(node.class_name, type_args: reduced_args)
88
+ reduce_nominal_app(node, reduced_args)
89
89
  when HktBody::AppRef
90
90
  reduced_args = node.args.map { |arg| walk(arg, bindings: bindings, state: state) }
91
91
  reduce_app_ref(node.uri, reduced_args, state: state)
@@ -96,6 +96,17 @@ module Rigor
96
96
  end
97
97
  end
98
98
 
99
+ def reduce_nominal_app(node, reduced_args)
100
+ normalized_name = node.class_name.sub(/\A::/, "")
101
+ if %w[Result Dry::Monads::Result].include?(normalized_name) && reduced_args.size == 2
102
+ Type::Combinator.result_of(reduced_args[0], reduced_args[1])
103
+ elsif %w[Maybe Dry::Monads::Maybe].include?(normalized_name) && reduced_args.size == 1
104
+ Type::Combinator.maybe_of(reduced_args[0])
105
+ else
106
+ Type::Combinator.nominal_of(node.class_name, type_args: reduced_args)
107
+ end
108
+ end
109
+
99
110
  # ADR-20 § D3 conditional reduction. Resolves the test against the current bindings and
100
111
  # picks a branch:
101
112
  #
@@ -150,7 +150,9 @@ module Rigor
150
150
  # loaded RBS env, parse them via {Rigor::RbsExtended::HktDirectives}, and return a new
151
151
  # registry that is the union of `base` and every parsed entry. Last-write-wins on URI
152
152
  # collisions per {#merge}'s contract. Fail-soft on per-annotation parse errors (the reporter
153
- # records an `:info` entry; the other annotations still apply).
153
+ # records an entry the run surfaces as one `dynamic.rbs-extended.hkt-directive-invalid` `:info`
154
+ # diagnostic; the other annotations still apply). That claim was false until issue #785: the
155
+ # production reporter has no `#record`, so every declined directive was dropped silently.
154
156
  #
155
157
  # @param rbs_loader [Rigor::Environment::RbsLoader]
156
158
  # @param base [HktRegistry] starting registry (typically the bundled
@@ -158,8 +160,11 @@ module Rigor
158
160
  # @param name_scope [Rigor::Environment::NameScope, nil] threaded through to the bound
159
161
  # resolver for class-name lookups; safe to omit during scanning since hkt bounds are
160
162
  # typically `untyped` or stdlib classes.
161
- # @param reporter [#record, nil] same fail-soft reporter contract the other RBS-extended
162
- # parsers use.
163
+ # @param reporter [Rigor::RbsExtended::Reporter, #record, nil] same fail-soft reporter contract
164
+ # the other RBS-extended parsers use; a collecting double that responds only to `#record` /
165
+ # `#<<` still works (see {Rigor::RbsExtended::HktDirectives.record_hkt_error}).
166
+
167
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
163
168
  def self.scan_rbs_loader(rbs_loader, base: EMPTY, name_scope: nil, reporter: nil)
164
169
  return base if rbs_loader.nil?
165
170
 
@@ -182,6 +187,59 @@ module Rigor
182
187
  definitions << defn if defn
183
188
  end
184
189
 
190
+ require_relative "hkt_sugar_translator"
191
+ if rbs_loader.respond_to?(:each_type_alias_decl)
192
+ rbs_loader.each_type_alias_decl do |type_name, decl_entry|
193
+ decl = decl_entry.decl
194
+ next if decl.type_params.empty?
195
+
196
+ uri_str = type_name.to_s.sub(/\A::/, "")
197
+ next unless uri_str.include?("::")
198
+
199
+ uri = uri_str.to_sym
200
+
201
+ params = decl.type_params.map(&:name)
202
+ params_set = params.to_set
203
+
204
+ # No rescue around this translate. HktSugarTranslator degrades every alias shape it
205
+ # cannot model (a non-recursive alias, a malformed self-reference) to a leaf, so a
206
+ # parseable `type` alias never raises here — that is the fix for issue #776, where an
207
+ # unknown keyword aborted this shared, memoised build and turned every file into an
208
+ # `internal analyzer error`. An exception that still reaches this point is an analyzer
209
+ # bug and MUST propagate rather than silently dropping the alias, matching
210
+ # RbsLoader#each_known_class_name's fail-soft contract. It propagates to the seam in
211
+ # `Environment#hkt_registry` (issue #784), which records it and degrades to the pre-scan
212
+ # registry so it surfaces once per run instead of reaching every file's analysis.
213
+ translator = HktSugarTranslator.new(uri: uri, params_set: params_set)
214
+ body_tree = translator.translate(decl.type)
215
+
216
+ next unless translator.recursive
217
+
218
+ variance = decl.type_params.map do |p|
219
+ case p.variance
220
+ when :covariant then :out
221
+ when :contravariant then :in
222
+ else :inv
223
+ end
224
+ end
225
+
226
+ registrations << Registration.new(
227
+ uri: uri,
228
+ arity: params.size,
229
+ variance: variance,
230
+ bound: Type::Combinator.untyped
231
+ )
232
+ definitions << Definition.new(
233
+ uri: uri,
234
+ params: params,
235
+ body: "",
236
+ body_tree: body_tree,
237
+ source_path: decl.location&.buffer&.name,
238
+ source_line: decl.location&.start_line
239
+ )
240
+ end
241
+ end
242
+
185
243
  return base if registrations.empty? && definitions.empty?
186
244
 
187
245
  overlay = new(registrations: registrations, definitions: definitions)
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+ require_relative "hkt_body"
5
+
6
+ module Rigor
7
+ module Inference
8
+ class HktSugarTranslator
9
+ attr_reader :recursive
10
+
11
+ def initialize(uri:, params_set:)
12
+ @uri = uri
13
+ @params_set = params_set
14
+ @recursive = false
15
+ end
16
+
17
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
18
+ def translate(type)
19
+ case type
20
+ when RBS::Types::Alias
21
+ if type.name.to_s.sub(/\A::/, "") == @uri.to_s && !type.args.empty?
22
+ @recursive = true
23
+ args = type.args.map { |a| translate(a) }
24
+ HktBody::AppRef.new(uri: @uri, args: args)
25
+ else
26
+ # Everything else degrades to a concrete leaf: HktBody has no AliasRef node and Rigor
27
+ # resolves non-HKT aliases through RbsLoader. A bare self-reference with no type
28
+ # arguments (`type Foo::x[T] = ... | Foo::x`) lands here too — it is malformed for a
29
+ # parameterized alias (`rbs validate` rejects it), so it must not build an empty-args
30
+ # `AppRef`, which `HktBody` rejects.
31
+ fallback_to_type_leaf(type)
32
+ end
33
+ when RBS::Types::ClassInstance
34
+ class_name = type.name.to_s.sub(/\A::/, "")
35
+ if type.args.empty?
36
+ HktBody::TypeLeaf.new(type: Rigor::Type::Nominal.new(class_name))
37
+ else
38
+ args = type.args.map { |a| translate(a) }
39
+ HktBody::NominalApp.new(class_name: class_name, args: args)
40
+ end
41
+ when RBS::Types::Variable
42
+ if @params_set.include?(type.name)
43
+ HktBody::Param.new(name: type.name)
44
+ else
45
+ fallback_to_type_leaf(type)
46
+ end
47
+ when RBS::Types::Union
48
+ HktBody::Union.new(arms: type.types.map { |t| translate(t) })
49
+ when RBS::Types::Optional
50
+ HktBody::Union.new(arms: [
51
+ translate(type.type),
52
+ HktBody::TypeLeaf.new(type: Rigor::Type::Constant.new(nil))
53
+ ])
54
+ when RBS::Types::Bases::Nil
55
+ HktBody::TypeLeaf.new(type: Rigor::Type::Constant.new(nil))
56
+ when RBS::Types::Bases::Bool
57
+ HktBody::TypeLeaf.new(type: Rigor::Type::Combinator.union(Rigor::Type::Constant.new(true), Rigor::Type::Constant.new(false)))
58
+ when RBS::Types::Bases::Any
59
+ HktBody::TypeLeaf.new(type: Rigor::Type::Combinator.untyped)
60
+ else
61
+ fallback_to_type_leaf(type)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ # The `else` arm and the unbound-variable / non-recursive-alias arms all degrade a subterm the
68
+ # HKT body grammar has no node for to a concrete `Rigor::Type` leaf. `RbsTypeTranslator` has no
69
+ # name-scope parameter — it never resolves relative names — and the alias decls this walk reads
70
+ # come off a `resolve_type_names`-d environment, so names arrive already absolute and there is
71
+ # nothing a scope would do here. `alias_expander: nil` is deliberate too: a nested alias reached
72
+ # in this fallback degrades to `Dynamic[Top]` rather than pulling a second alias body into an
73
+ # HKT definition.
74
+ def fallback_to_type_leaf(type)
75
+ require_relative "rbs_type_translator" unless defined?(RbsTypeTranslator)
76
+ rigor_type = RbsTypeTranslator.translate(
77
+ type,
78
+ alias_expander: nil,
79
+ type_vars: {},
80
+ self_type: Rigor::Type::Combinator.untyped,
81
+ instance_type: Rigor::Type::Combinator.untyped
82
+ )
83
+ HktBody::TypeLeaf.new(type: rigor_type)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "mutation_widening"
6
+
7
+ module Rigor
8
+ module Inference
9
+ # `h[k] ||= v`, `h[k] &&= v` and `h[k] += v` store through `[]=`, but Prism gives each its own node class rather
10
+ # than a `[]=` `CallNode`. None of them therefore reached {MutationWidening.widen_after_call}, and a literal-shape
11
+ # binding outlived its justification: an `@h = {}` written only through `@h[k] ||= {}` kept its empty `HashShape`,
12
+ # so `@h.empty?` folded to `Constant[true]` and `return nil if x.nil? || @h.empty?` drew a false
13
+ # `flow.always-truthy-condition` on a hash the class fills.
14
+ #
15
+ # Two consumers need the same recognition and neither had it: {StatementEvaluator} for the straight-line write, and
16
+ # {ScopeIndexer}'s class-ivar pre-pass for the cross-method one (an ivar written in `add` and read in `probe`).
17
+ # `||=` / `&&=` are conditional at runtime, but a widening may only LOSE precision, so answering on the branch that
18
+ # does not store is safe.
19
+ #
20
+ # `Prism::IndexTargetNode` is deliberately absent: it is a multi-assign TARGET, and the `MultiWriteNode` that owns
21
+ # it is where that write is observed.
22
+ module IndexWriteWidening
23
+ NODE_CLASSES = [Prism::IndexOrWriteNode, Prism::IndexAndWriteNode, Prism::IndexOperatorWriteNode].freeze
24
+
25
+ # The method these forms store through — the name the mutator tables are keyed on.
26
+ MUTATOR = :[]=
27
+
28
+ module_function
29
+
30
+ # @param node [Prism::Node]
31
+ def index_write?(node)
32
+ NODE_CLASSES.any? { |klass| node.is_a?(klass) }
33
+ end
34
+
35
+ # `arg_types` is the `[key, stored_value]` pair the caller typed, shaped like a `[]=` call's argument list so
36
+ # the widening joins the stored value into the carrier's content evidence exactly as a real `[]=` does
37
+ # (issue #560). Empty means "no evidence" and widens without joining.
38
+ #
39
+ # @param node [Prism::Node] one of {NODE_CLASSES}
40
+ # @param current_scope [Rigor::Scope]
41
+ # @param arg_types [Array<Rigor::Type::Base>]
42
+ # @return [Rigor::Scope]
43
+ def widen(node:, current_scope:, arg_types: MutationWidening::NO_ARG_TYPES)
44
+ MutationWidening.widen_receiver_aliases(node.receiver, MUTATOR, current_scope, arg_types: arg_types)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -83,6 +83,20 @@ module Rigor
83
83
  [receiver.first, receiver.last, key]
84
84
  end
85
85
 
86
+ # Issue #544 — whether the RECEIVER's type is precise enough for a recorded slot value to be a
87
+ # fact. A `Dynamic` / `Top` constituent means the collection can hold a caller-supplied value at
88
+ # the slot that `||=` keeps, so recording the default alone would invent a fact (mail's
89
+ # `options[:count] ||= :all` folded a reachable `count: 1` path away). Unions walk their members;
90
+ # everything else — the tracked `HashShape` / `Tuple` carriers this feature was built for, and
91
+ # plain nominals whose RBS read already answers honestly — passes.
92
+ def fully_tracked_receiver_type?(type)
93
+ case type
94
+ when Type::Dynamic, Type::Top then false
95
+ when Type::Union then type.members.all? { |m| fully_tracked_receiver_type?(m) }
96
+ else true
97
+ end
98
+ end
99
+
86
100
  # Looks up a recorded narrowing for `receiver[key]` against `scope`, returning the narrowed
87
101
  # type or nil when no entry applies. Used by ExpressionTyper's `[]` dispatch to refine the
88
102
  # result of a stable indexed read.
@@ -136,7 +136,7 @@ module Rigor
136
136
  end
137
137
 
138
138
  # @return [:always_true, :always_false, :bool, nil]
139
- # rubocop:disable Metrics/CyclomaticComplexity
139
+ # rubocop:disable-next Metrics/CyclomaticComplexity
140
140
  def predicate_decision(method_name, truthiness, emptiness)
141
141
  case method_name
142
142
  when :all?
@@ -159,7 +159,6 @@ module Rigor
159
159
  :bool
160
160
  end
161
161
  end
162
- # rubocop:enable Metrics/CyclomaticComplexity
163
162
 
164
163
  def bool_union
165
164
  Type::Combinator.union(
@@ -185,10 +184,13 @@ module Rigor
185
184
  end
186
185
 
187
186
  def constant_emptiness(value)
188
- # Only `Range` constants reach these folds: `Type::Constant` rejects Array / Hash literals (they
189
- # become `Tuple` / `HashShape` carriers), and the remaining scalar constants (Integer / Float /
190
- # Symbol / String / ) are not Enumerable receivers for the filter or predicate methods folded here.
187
+ # `Range` constants and folded `Set` values (SetFolding wraps a real frozen ::Set) are the
188
+ # Enumerable receivers that reach these folds: `Type::Constant` rejects Array / Hash literals
189
+ # (they become `Tuple` / `HashShape` carriers), and the remaining scalar constants (Integer /
190
+ # Float / Symbol / String / …) are not Enumerable receivers for the filter or predicate methods
191
+ # folded here.
191
192
  return range_emptiness(value) if value.is_a?(Range)
193
+ return (value.empty? ? :empty : :non_empty) if value.is_a?(::Set)
192
194
 
193
195
  :unknown
194
196
  end
@@ -220,6 +220,16 @@ module Rigor
220
220
  method_name = context.method_name
221
221
  args = context.args
222
222
 
223
+ # Issue #539 — every fold in this tier executes the BLOCKLESS form of the method against the
224
+ # constant value. A call site that passes a block (literal or `&arg`) has different semantics —
225
+ # `SET.any? { |n| lookup(n) }` folded to the blockless `Set#any?`'s `true` on any non-empty set,
226
+ # a wrong type the flow rules then amplified (`flow.always-truthy-condition` on our own lib).
227
+ # The catalog's `block_dependent` classification covers the C body's own iterator, not the call
228
+ # site's block, so the guard belongs here. Internal per-element dispatches hand a non-CallNode
229
+ # (e.g. the `&:sym` BlockArgumentNode) as `call_node` — those carry no call-site block.
230
+ call_node = context.call_node
231
+ return nil if call_node.is_a?(Prism::CallNode) && call_node.block
232
+
223
233
  if REFLECTIVE_SEND_METHODS.include?(method_name)
224
234
  first_arg = args.first
225
235
  return nil unless first_arg.is_a?(Type::Constant) && first_arg.value.is_a?(Symbol)
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+ require_relative "singleton_folding"
5
+
6
+ module Rigor
7
+ module Inference
8
+ module MethodDispatcher
9
+ # Folds `JSON.generate` / `JSON.pretty_generate` to their precise return type.
10
+ #
11
+ # Both methods always return a `String` or raise (`JSON::GeneratorError`, `JSON::NestingError`, …) —
12
+ # never `nil`, never a non-`String` value. Upstream RBS (`stdlib/json/0/json.rbs`) already declares
13
+ # `generate` as `-> String`, but `pretty_generate` is declared `-> untyped`, so every
14
+ # `JSON.pretty_generate` call widens to `Dynamic[top]` and every downstream method call on the result
15
+ # loses precision with it. This tier closes that gap by construction rather than waiting on an
16
+ # upstream RBS fix, and folds `generate` alongside it for symmetry — a `Nominal[String]` answer here
17
+ # is monotone over both today's `untyped` (`pretty_generate`) and today's already-precise `String`
18
+ # (`generate`).
19
+ #
20
+ # Unlike `CGIFolding` / `ShellwordsFolding`, this tier does NOT evaluate the argument. `JSON.generate`
21
+ # is not a pure function whose *result* can be computed at analysis time in general — the object graph
22
+ # can be arbitrarily large, cyclic, or rely on a user-defined `#to_json` — so only the *return type* is
23
+ # folded here, never the *return value*. A future literal-argument constant fold (mirroring
24
+ # `ConstantFolding`'s treatment of other pure stdlib calls) can layer on top of this tier without
25
+ # changing it.
26
+ #
27
+ # === Supported methods
28
+ #
29
+ # * `generate(obj)` / `generate(obj, opts)` — returns `Nominal[String]`.
30
+ # * `pretty_generate(obj)` / `pretty_generate(obj, opts)` — returns `Nominal[String]`.
31
+ #
32
+ # === Unsupported cases
33
+ #
34
+ # Returns `nil` (deferring to the next dispatcher tier) when:
35
+ # - the receiver is not `Singleton[JSON]`,
36
+ # - the method is not `generate` / `pretty_generate` (e.g. `JSON.parse`, `JSON.dump`),
37
+ # - the call has the wrong arity (not 1 or 2 positional arguments).
38
+ module JSONFolding
39
+ JSON_GENERATE_METHODS = Set[:generate, :pretty_generate].freeze
40
+ private_constant :JSON_GENERATE_METHODS
41
+
42
+ module_function
43
+
44
+ # @return [Rigor::Type, nil] folded result, or nil to defer.
45
+ def try_dispatch(context)
46
+ receiver = context.receiver
47
+ method_name = context.method_name
48
+ args = context.args
49
+ return nil unless SingletonFolding.receiver?(receiver, "JSON")
50
+ return nil unless JSON_GENERATE_METHODS.include?(method_name)
51
+ return nil unless args.size.between?(1, 2)
52
+
53
+ Type::Combinator.nominal_of("String")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -80,10 +80,22 @@ module Rigor
80
80
  # declaration.
81
81
  # @return [RBS::MethodType, nil] the chosen overload, or nil when the definition has no method
82
82
  # types at all.
83
- def select(method_definition, arg_types:, self_type:, instance_type:, type_vars: {}, block_required: false,
84
- environment: nil)
83
+ def select(method_definition, **) = select_candidates(method_definition, **).first
84
+
85
+ # Issue #521 — like {.select}, but when a `Dynamic[Top]` argument reaches the gradual pass it
86
+ # returns EVERY gradually-matching overload instead of pinning the first. An untyped argument
87
+ # accepts every param indiscriminately, so "first gradual match" is decided by overload-list
88
+ # position, not by types — `[true] * n` with an untyped `n` pinned `Array#*(string) -> String`
89
+ # and answered a wrong precise type the runtime can contradict. The caller unions the candidates'
90
+ # returns, which contains the truth whichever overload the runtime takes. Every other path (strict,
91
+ # alias-resolved, typed-gradual, arity fallback) still yields exactly one candidate, so `select`
92
+ # keeps its historical single answer.
93
+ #
94
+ # @return [Array<RBS::MethodType>] matching overloads; empty when the definition declares none.
95
+ def select_candidates(method_definition, arg_types:, self_type:, instance_type:, type_vars: {},
96
+ block_required: false, environment: nil)
85
97
  overloads = method_definition.method_types
86
- return nil if overloads.empty?
98
+ return [] if overloads.empty?
87
99
 
88
100
  # `rigor:v1:param: <name> <refinement>` annotations on this method override the RBS-declared
89
101
  # parameter type at the matching name. The map is consumed inside `accepts_param?` so overload
@@ -96,15 +108,18 @@ module Rigor
96
108
  # an arbitrary sibling-class arm that only wins by overload-list position.
97
109
  overloads = ReceiverAffinity.reorder(overloads, self_type: self_type, environment: environment)
98
110
 
111
+ alias_expander = environment&.rbs_loader
99
112
  passes = lambda do |require_block|
100
113
  run_selection_passes(
101
- overloads, arg_types: arg_types, self_type: self_type, instance_type: instance_type,
102
- type_vars: type_vars, block_required: require_block, param_overrides: param_overrides
114
+ overloads,
115
+ { arg_types: arg_types, self_type: self_type, instance_type: instance_type,
116
+ type_vars: type_vars, block_required: require_block, param_overrides: param_overrides,
117
+ alias_expander: alias_expander }
103
118
  )
104
119
  end
105
120
 
106
- match = passes.call(block_required)
107
- return match if match
121
+ matches = passes.call(block_required)
122
+ return matches unless matches.empty?
108
123
 
109
124
  # A block at the call site that no block-declaring overload matched: Ruby ignores a block handed
110
125
  # to a method that never yields it, so retry treating the block as ignorable rather than failing
@@ -112,8 +127,8 @@ module Rigor
112
127
  # `define_command(:x) do … end` against `def define_command: (Symbol) -> Symbol`) degraded to
113
128
  # `Dynamic[Top]` — and on a self-send suppressed the whole method's return type.
114
129
  if block_required
115
- match = passes.call(false)
116
- return match if match
130
+ matches = passes.call(false)
131
+ return matches unless matches.empty?
117
132
  end
118
133
 
119
134
  # No (usable) block at the call site: prefer an overload that does not REQUIRE a block over
@@ -121,7 +136,7 @@ module Rigor
121
136
  # overload first (`() { ... } -> Array[Elem]`) and the bare-call overload second
122
137
  # (`() -> Enumerator[...]`). Without this, a no-block `[1, 2].filter` would adopt the block
123
138
  # overload's `Array[Elem]` return when the call actually yields an `Enumerator`.
124
- overloads.find { |mt| !overload_requires_block?(mt) } || overloads.first
139
+ [overloads.find { |mt| !overload_requires_block?(mt) } || overloads.first]
125
140
  end
126
141
 
127
142
  def overload_has_block?(method_type)
@@ -147,38 +162,58 @@ module Rigor
147
162
  # `Array#*(int)` wins over the `Array#*(string) -> String` overload for Integer args.
148
163
  # - Pass 2 (gradual): the original gradual matcher so overloads that legitimately rely on
149
164
  # duck-typed params still resolve when nothing stricter applies.
150
- def run_selection_passes(overloads, arg_types:, self_type:, instance_type:, type_vars:, block_required:,
151
- param_overrides:)
152
- shared = {
153
- arg_types: arg_types, self_type: self_type, instance_type: instance_type,
154
- type_vars: type_vars, block_required: block_required, param_overrides: param_overrides
155
- }
156
- find_matching_overload(overloads, **shared, strict: true) ||
157
- find_matching_overload_via_aliases(overloads, arg_types: arg_types, block_required: block_required) ||
158
- find_matching_overload(overloads, **shared, strict: false)
165
+ # `shared` is the caller-assembled keyword bundle for `find_matching_overload` — hash-shaped
166
+ # because the pass pipeline forwards it twice and RuboCop's parameter-list budget is real.
167
+ def run_selection_passes(overloads, shared)
168
+ strict = find_matching_overload(overloads, **shared, strict: true)
169
+ return strict unless strict.empty?
170
+
171
+ alias_hit = find_matching_overload_via_aliases(
172
+ overloads, arg_types: shared[:arg_types], block_required: shared[:block_required]
173
+ )
174
+ return [alias_hit] if alias_hit
175
+
176
+ # Pass 2, array-valued. With every argument carrying real type information the first gradual
177
+ # match keeps its historical single-winner contract. With a `Dynamic[Top]` argument in play the
178
+ # matches are indistinguishable by types — position alone would pick — so ALL of them come back
179
+ # and the dispatch layer unions their returns (#521).
180
+ matches = find_matching_overload(overloads, **shared, strict: false)
181
+ return matches.first(1) unless shared[:arg_types].any? { |t| untyped_arg?(t) }
182
+
183
+ matches
159
184
  end
160
185
 
161
- # rubocop:disable Metrics/ParameterLists
186
+ # rubocop:disable-next Metrics/ParameterLists
162
187
  def find_matching_overload(overloads, arg_types:, self_type:, instance_type:, type_vars:, block_required:,
163
- param_overrides:, strict:)
164
- return nil if strict && arg_types.any? { |t| untyped_arg?(t) }
188
+ param_overrides:, strict:, alias_expander: nil)
189
+ return [] if strict && arg_types.any? { |t| untyped_arg?(t) }
165
190
 
166
- overloads.find do |method_type|
167
- next false if block_required && !OverloadSelector.overload_has_block?(method_type)
168
- next false if !block_required && OverloadSelector.overload_requires_block?(method_type)
191
+ predicate = lambda do |method_type|
192
+ next false unless engages_block_shape?(method_type, block_required)
169
193
  next false if strict && !strictly_typed_params?(method_type, arg_types.size)
170
194
 
171
195
  matches?(
172
- method_type,
173
- arg_types,
174
- self_type: self_type,
175
- instance_type: instance_type,
176
- type_vars: type_vars,
177
- param_overrides: param_overrides
196
+ method_type, arg_types,
197
+ self_type: self_type, instance_type: instance_type,
198
+ type_vars: type_vars, param_overrides: param_overrides,
199
+ alias_expander: alias_expander
178
200
  )
179
201
  end
202
+ # Strict keeps its historical first-match short-circuit (a dispatch hot path); the gradual
203
+ # pass needs the full candidate list for the #521 union.
204
+ return [overloads.find(&predicate)].compact if strict
205
+
206
+ overloads.select(&predicate)
207
+ end
208
+
209
+ # Whether the overload's block clause is compatible with the call site's block shape: a
210
+ # block-bearing call engages only block-declaring overloads; a block-less call skips overloads
211
+ # that REQUIRE one.
212
+ def engages_block_shape?(method_type, block_required)
213
+ return OverloadSelector.overload_has_block?(method_type) if block_required
214
+
215
+ !OverloadSelector.overload_requires_block?(method_type)
180
216
  end
181
- # rubocop:enable Metrics/ParameterLists
182
217
 
183
218
  # Treats the literal `untyped` carrier (`Dynamic[Top]`) as too imprecise to drive a strict-pass
184
219
  # match. Other `Dynamic`-wrapped types with a concrete static facet carry enough information to
@@ -194,9 +229,13 @@ module Rigor
194
229
  # to `Dynamic[Top]` at the param level. Only fires when EVERY positional param has a known
195
230
  # alias-or-strict shape; otherwise gradual matching takes over.
196
231
  def find_matching_overload_via_aliases(overloads, arg_types:, block_required:)
232
+ # Issue #521 — an untyped argument "maybe"-accepts EVERY alias's strict arm, so it cannot
233
+ # discriminate between overloads here any more than in the strict pass; without this guard a
234
+ # Dynamic arg pinned `Array#*(string) -> String` purely by declaration order.
235
+ return nil if arg_types.any? { |t| untyped_arg?(t) }
236
+
197
237
  overloads.find do |method_type|
198
- next false if block_required && !OverloadSelector.overload_has_block?(method_type)
199
- next false if !block_required && OverloadSelector.overload_requires_block?(method_type)
238
+ next false unless engages_block_shape?(method_type, block_required)
200
239
 
201
240
  fun = method_type.type
202
241
  next false unless arity_compatible?(fun, arg_types.size)
@@ -278,7 +317,8 @@ module Rigor
278
317
  end
279
318
  end
280
319
 
281
- def matches?(method_type, arg_types, self_type:, instance_type:, type_vars:, param_overrides:)
320
+ def matches?(method_type, arg_types, self_type:, instance_type:, type_vars:, param_overrides:,
321
+ alias_expander: nil)
282
322
  return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
283
323
 
284
324
  fun = method_type.type
@@ -292,7 +332,8 @@ module Rigor
292
332
  self_type: self_type,
293
333
  instance_type: instance_type,
294
334
  type_vars: type_vars,
295
- param_overrides: param_overrides
335
+ param_overrides: param_overrides,
336
+ alias_expander: alias_expander
296
337
  )
297
338
  end
298
339
  end
@@ -346,12 +387,14 @@ module Rigor
346
387
  head
347
388
  end
348
389
 
349
- def accepts_param?(param, arg, self_type:, instance_type:, type_vars:, param_overrides:)
390
+ def accepts_param?(param, arg, self_type:, instance_type:, type_vars:, param_overrides:,
391
+ alias_expander: nil)
350
392
  param_type = param_overrides[param.name] || RbsTypeTranslator.translate(
351
393
  param.type,
352
394
  self_type: self_type,
353
395
  instance_type: instance_type,
354
- type_vars: type_vars
396
+ type_vars: type_vars,
397
+ alias_expander: alias_expander
355
398
  )
356
399
  # An `untyped` arg gradually accepts against every param, so a value-pinning param would be
357
400
  # "matched" with zero evidence and its value-precise return (`(nil) -> []`) would beat broader