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
@@ -3,6 +3,7 @@
3
3
  require_relative "../../type"
4
4
  require_relative "singleton_folding"
5
5
  require_relative "hash_transform_keys_folding"
6
+ require_relative "integer_step_block_params"
6
7
 
7
8
  module Rigor
8
9
  module Inference
@@ -23,6 +24,10 @@ module Rigor
23
24
  # bound from the argument.
24
25
  # - `a.downto(b) { |i| … }` yields the same domain `[b, a]`, just iterated in reverse. Lower bound
25
26
  # from the argument, upper bound from the receiver.
27
+ # - `a.step(limit, by) { |i| … }` (and the `by:` / `to:` keyword forms) yields `Integer` when the receiver,
28
+ # the limit and the step are all Integer; any other operand keeps the RBS `(Numeric)` binding
29
+ # (`1.step(10, 0.5)` yields Floats). RBS declares every `Numeric#step` block as `(Numeric)`, which reports
30
+ # `even?` on correct Integer loops (issue #1783); see {IntegerStepBlockParams}.
26
31
  # - `h.transform_keys(mapping) { |k| … }` yields the receiver's keys, a rule the RBS probe does not state for
27
32
  # a union of receiver shapes ({HashTransformKeysFolding.block_param_types}).
28
33
  module IteratorDispatch
@@ -38,6 +43,7 @@ module Rigor
38
43
  when :times then times_block_params(receiver)
39
44
  when :upto then upto_block_params(receiver, args.first)
40
45
  when :downto then downto_block_params(receiver, args.first)
46
+ when :step then IntegerStepBlockParams.block_param_types(receiver, args)
41
47
  when :each_with_index then each_with_index_block_params(receiver)
42
48
  when :each_with_object then each_with_object_block_params(receiver, args.first)
43
49
  when :inject, :reduce then inject_block_params(receiver, args)
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # How `OverloadSelector` reads a call's keyword arguments (#1727).
9
+ #
10
+ # A call's arguments reach the selector as one positional list, the keyword hash (`f(a, k: 1)`) typed as its
11
+ # last entry. Only the AST tells it from a braced positional hash (`f(a, { k: 1 })`), so the caller says which
12
+ # it is (`keywords_last`). An overload that declares keywords takes that hash as its keywords; one that
13
+ # declares none reads it as a trailing positional `Hash`, as Ruby passes it.
14
+ module KeywordArguments
15
+ NO_PARAMS = [].freeze
16
+ private_constant :NO_PARAMS
17
+
18
+ # The most argument lists {.distributions} spells out; past it, the block probe answers no information.
19
+ DISTRIBUTION_LIMIT = 8
20
+ private_constant :DISTRIBUTION_LIMIT
21
+
22
+ module_function
23
+
24
+ # The actuals `method_type`'s positional parameters read: every argument, less the trailing keyword hash
25
+ # when the call passes one and the overload declares keywords to take it.
26
+ def positional(method_type, arg_types, keywords_last)
27
+ return arg_types unless keywords_last && !arg_types.empty? && declares?(method_type.type)
28
+
29
+ arg_types[0...-1]
30
+ end
31
+
32
+ # {.positional} for the selector's `shared` bundle, computed once per selection: every overload that takes the
33
+ # keyword hash reads the same trimmed list. A bundle rebuilt with other arguments must reset the cache.
34
+ def selection_positional(method_type, shared)
35
+ return shared[:arg_types] unless shared[:keywords_last] && declares?(method_type.type)
36
+
37
+ shared[:positional_arguments] ||= positional(method_type, shared[:arg_types], true)
38
+ end
39
+
40
+ # The argument lists a call with a keyword hash stands for, one per combination of the members of its
41
+ # union-typed keyword values (`headers: bool` stands for `headers: true` and `headers: false`, and so does a
42
+ # `Dynamic[bool]`, which the #521 join produces), or nil when that is more than {DISTRIBUTION_LIMIT} lists. A
43
+ # precise union value selects per member at runtime, so an answer that holds for the call must hold for every
44
+ # member (#1737). Only a key that discriminates between `method_types` ({.discriminating?}) splits (#1779):
45
+ # every overload reads any other key's members alike, so splitting it only multiplies the lists toward the
46
+ # limit (`(a: Symbol, b: Symbol)` with two three-member values made nine). An overload that declares no
47
+ # keywords but may take the hash as a positional `Hash` ({.positional_hash_reader?}, whose block says whether
48
+ # a positional parameter may accept a `Hash`) reads every value against that parameter, so then every union
49
+ # value splits. `count` is the call's argument count, nil when a splat hides it. Without a keyword hash, or
50
+ # with no discriminating union value, the one list, `arg_types` itself.
51
+ def distributions(arg_types, keywords_last, method_types, count = arg_types.size, &hash_param)
52
+ keywords = keywords_last && arg_types.last
53
+ return [arg_types] unless keywords.is_a?(Type::HashShape)
54
+
55
+ choices = split_choices(keywords, method_types) do
56
+ hash_param && positional_hash_reader?(method_types, count, &hash_param)
57
+ end
58
+ return [arg_types] if choices.nil?
59
+ return nil if choices.reduce(1) { |count, (_, members)| count * members.size } > DISTRIBUTION_LIMIT
60
+
61
+ combinations(choices).map do |pairs|
62
+ shape = Type::HashShape.new(
63
+ pairs, required_keys: keywords.required_keys, optional_keys: keywords.optional_keys,
64
+ read_only_keys: keywords.read_only_keys, extra_keys: keywords.extra_keys
65
+ )
66
+ arg_types[0...-1] + [shape]
67
+ end
68
+ end
69
+
70
+ # Each key of `keywords` with the values its lists take: a discriminating union value's members, any other
71
+ # value whole. Nil when no value splits. The block answers whether every union value splits (read once).
72
+ def split_choices(keywords, method_types)
73
+ split = false
74
+ every = nil
75
+ choices = keywords.pairs.map do |name, value|
76
+ members = union_members(value)
77
+ next [name, [value]] unless members
78
+
79
+ unless discriminating?(name, method_types)
80
+ every = yield ? true : false if every.nil?
81
+ next [name, [value]] unless every
82
+ end
83
+
84
+ split = true
85
+ [name, members]
86
+ end
87
+ choices if split
88
+ end
89
+
90
+ # Whether the members of `name`'s value may select different overloads among `method_types`: the overloads
91
+ # that take the key (by name or through `**rest`) declare it with different types (`headers: true` in one,
92
+ # `?headers: false` in another). Where they all declare one type, a member it rejects is rejected by every
93
+ # overload, so the split only adds lists: four `flag: bool` keywords alone made sixteen. An overload that
94
+ # declares others but not this key rejects it whatever its value. One that declares no keywords reads the
95
+ # hash positionally, which {.positional_hash_reader?} decides for every key at once.
96
+ def discriminating?(name, method_types)
97
+ first = nil
98
+ method_types.any? do |method_type|
99
+ fun = method_type.type
100
+ next false unless declares?(fun)
101
+
102
+ type = (fun.required_keywords[name] || fun.optional_keywords[name] || fun.rest_keywords)&.type
103
+ next false if type.nil?
104
+
105
+ first ||= type
106
+ type != first
107
+ end
108
+ end
109
+
110
+ # Whether an overload of `method_types` that declares no keywords takes `count` arguments and may read the last,
111
+ # the keyword hash, as a positional `Hash`: the block answers for the RBS parameter at that position (the
112
+ # `(String)` of `(a: Symbol) | (String)` cannot take one, the `(Hash[Symbol, String])` of
113
+ # `(a: Integer) | (Hash[Symbol, String])` can, and its members then select per value), and an untyped `(?)`
114
+ # parameter list may take anything. A nil `count` (a splat hides how many arguments precede the hash, and
115
+ # `ph(*[], a: v)` passes `{ a: v }` to a one-parameter `(Hash)`) asks about every positional parameter.
116
+ def positional_hash_reader?(method_types, count, &)
117
+ method_types.any? do |method_type|
118
+ fun = method_type.type
119
+ next false if declares?(fun)
120
+ next true unless fun.respond_to?(:required_positionals)
121
+ next positional_params(fun).any?(&) if count.nil?
122
+
123
+ param = positional_param_at(fun, count, count - 1)
124
+ !param.nil? && yield(param)
125
+ end
126
+ end
127
+
128
+ def positional_params(fun)
129
+ params = fun.required_positionals + fun.optional_positionals + fun.trailing_positionals
130
+ fun.rest_positionals ? params + [fun.rest_positionals] : params
131
+ end
132
+
133
+ # The positional parameter `fun` binds the argument at `index` to when called with `count` arguments, or nil
134
+ # when the count does not fit its arity.
135
+ def positional_param_at(fun, count, index)
136
+ required = fun.required_positionals
137
+ trailing = fun.trailing_positionals
138
+ optional = fun.optional_positionals
139
+ minimum = required.size + trailing.size
140
+ return nil if count < minimum || (fun.rest_positionals.nil? && count > minimum + optional.size)
141
+ return required[index] if index < required.size
142
+ return trailing[index - (count - trailing.size)] if index >= count - trailing.size
143
+
144
+ optional[index - required.size] || fun.rest_positionals
145
+ end
146
+
147
+ # The members a keyword value splits into: a union's, or those of a `Dynamic` whose static facet is a union,
148
+ # less `nil`, which the #521 join usually carries from an arm no call takes (as `FacetDistribution` reads a
149
+ # facet). One member left stands in for the value (`Dynamic[false | nil]` reads as `false`), since the bare
150
+ # `Dynamic` would gradually match every overload's keyword. A plain union keeps `nil`: there it is a value
151
+ # the call may pass.
152
+ def union_members(value)
153
+ return value.members if value.is_a?(Type::Union)
154
+ return nil unless value.is_a?(Type::Dynamic) && value.static_facet.is_a?(Type::Union)
155
+
156
+ members = value.static_facet.members.reject { |member| nil_member?(member) }
157
+ members.empty? ? nil : members
158
+ end
159
+
160
+ def nil_member?(member)
161
+ return member.value.nil? if member.is_a?(Type::Constant)
162
+
163
+ member.is_a?(Type::Nominal) && member.class_name == "NilClass"
164
+ end
165
+
166
+ def combinations(choices)
167
+ choices.reduce([{}]) do |partials, (name, members)|
168
+ partials.flat_map { |partial| members.map { |member| partial.merge(name => member) } }
169
+ end
170
+ end
171
+
172
+ def declares?(fun)
173
+ return false unless fun.respond_to?(:required_keywords)
174
+
175
+ !fun.required_keywords.empty? || !fun.optional_keywords.empty? || !fun.rest_keywords.nil?
176
+ end
177
+
178
+ # The keyword hash `fun` takes as its keywords, or nil when the call passes none or `fun` declares none.
179
+ def keyword_hash(fun, arg_types, keywords_last)
180
+ return nil unless keywords_last && !arg_types.empty? && declares?(fun)
181
+
182
+ arg_types.last
183
+ end
184
+
185
+ # {.accepted?} for the selector's `shared` bundle: the keyword hash `fun` takes, if any, against its keywords.
186
+ def accepted_by?(fun, shared, strict, &)
187
+ accepted?(fun, keyword_hash(fun, shared[:arg_types], shared[:keywords_last]), strict, &)
188
+ end
189
+
190
+ def any_declares?(method_types) = method_types.any? { |method_type| declares?(method_type.type) }
191
+
192
+ # Issue #1800 — a keyword hash an overload declares and takes as its keywords is no positional argument of
193
+ # another. Both RBS (the first overload in declared order that takes the call) and Ruby (a method that accepts
194
+ # keywords binds `a: 1` to them) read `(a: Integer) -> Integer | (Object) -> String`'s `ob(a: 1)` as the
195
+ # keyword overload, but `ReceiverAffinity` moves `(Object)` first and the strict pass took it, reading the hash
196
+ # positionally. `matches` is a selection pass's answer for a call with a keyword hash; the block answers the
197
+ # keyword-declaring overloads' proven (`:yes`, every pair accepted with a `yes`) or gradual (`false`) matches.
198
+ # A `maybe` proves nothing: a source-only class is a `maybe` instance of every declared class, so
199
+ # `(a: Integer) | (Object)` would have taken `i(a: Bar.new)` as `Integer`. When one is proven, a
200
+ # positional reader declared after it is dropped. One declared before it is what RBS takes, while a method
201
+ # accepting keywords still binds them as keywords, so both stay and the caller joins their returns. A keyword
202
+ # overload that only gradually takes the hash (an unshaped `**opts`, an imprecise value) proves nothing either
203
+ # way, so it joins the positional readers rather than replace them.
204
+ def prefer_takers(declared, matches)
205
+ readers = matches.reject { |method_type| declares?(method_type.type) }
206
+ return matches if readers.empty?
207
+
208
+ takers = declared.select { |method_type| declares?(method_type.type) }
209
+ proven = yield(takers, :yes).first
210
+ unless proven
211
+ joined = matches | yield(takers, false)
212
+ return joined.size == matches.size ? matches : in_declared_order(declared, joined)
213
+ end
214
+
215
+ position = declared.index(proven)
216
+ earlier = readers.select { |method_type| declared.index(method_type) < position }
217
+ kept = matches.select { |method_type| declares?(method_type.type) }
218
+ in_declared_order(declared, kept | [proven] | earlier)
219
+ end
220
+
221
+ def in_declared_order(declared, method_types)
222
+ method_types.sort_by { |method_type| declared.index(method_type) }
223
+ end
224
+
225
+ # Whether `fun`'s keywords take `keywords` (nil for a call without a keyword hash). Without one, an
226
+ # overload that requires a keyword is not viable. With one, a closed shape must carry every required
227
+ # keyword, and every key it carries must be declared (or taken by `**rest`) with a value the block accepts
228
+ # against the declaration. An unshaped keyword hash (`f(**opts)` over a `Hash`) or a shape that may lack a
229
+ # required key is a gradual match only, never a strict one.
230
+ #
231
+ # @yieldparam param — the keyword's `RBS::Types::Function::Param`.
232
+ # @yieldparam value — the type the call passes for it.
233
+ def accepted?(fun, keywords, strict)
234
+ return true unless fun.respond_to?(:required_keywords)
235
+ return fun.required_keywords.empty? if keywords.nil?
236
+ return !strict unless keywords.is_a?(Type::HashShape)
237
+
238
+ required_present?(fun, keywords, strict) &&
239
+ keywords.pairs.all? do |name, value|
240
+ param = fun.required_keywords[name] || fun.optional_keywords[name] || fun.rest_keywords
241
+ !param.nil? && yield(param, value)
242
+ end
243
+ end
244
+
245
+ # The declarations the keys of the call's keyword hash land in, for the selector's `shared` bundle; empty
246
+ # without a shaped keyword hash `fun` takes (a key nothing declares lands nowhere).
247
+ def passed_params(fun, shared)
248
+ keywords = keyword_hash(fun, shared[:arg_types], shared[:keywords_last])
249
+ return NO_PARAMS unless keywords.is_a?(Type::HashShape)
250
+
251
+ keywords.pairs.each_key.filter_map do |name|
252
+ fun.required_keywords[name] || fun.optional_keywords[name] || fun.rest_keywords
253
+ end
254
+ end
255
+
256
+ def required_present?(fun, keywords, strict)
257
+ fun.required_keywords.each_key.all? do |name|
258
+ next true if keywords.pairs.key?(name) && !keywords.optional_key?(name)
259
+
260
+ # Possibly absent: an optional key, or a key an open shape may carry unseen.
261
+ !strict && (keywords.pairs.key?(name) || keywords.open?)
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../source/symbol_name"
3
4
  require_relative "../../type"
4
5
 
5
6
  module Rigor
@@ -94,7 +95,7 @@ module Rigor
94
95
 
95
96
  case arg.value
96
97
  when Symbol then arg.value
97
- when String then arg.value.to_sym
98
+ when String then Source::SymbolName.of(arg.value)
98
99
  end
99
100
  end
100
101
  end