rigortype 0.1.18 → 0.2.0

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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +159 -224
  3. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
  4. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
  5. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +32 -23
  6. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
  7. data/lib/rigor/analysis/check_rules/rule_walk.rb +151 -23
  8. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
  9. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
  10. data/lib/rigor/analysis/check_rules.rb +756 -132
  11. data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
  12. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
  13. data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
  14. data/lib/rigor/analysis/diagnostic.rb +8 -0
  15. data/lib/rigor/analysis/fact_store.rb +5 -4
  16. data/lib/rigor/analysis/rule_catalog.rb +153 -6
  17. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +19 -18
  18. data/lib/rigor/analysis/runner/project_pre_passes.rb +13 -9
  19. data/lib/rigor/analysis/runner.rb +75 -27
  20. data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
  21. data/lib/rigor/analysis/worker_session.rb +31 -25
  22. data/lib/rigor/bleeding_edge.rb +123 -0
  23. data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
  24. data/lib/rigor/cache/descriptor.rb +86 -8
  25. data/lib/rigor/cache/rbs_descriptor.rb +2 -1
  26. data/lib/rigor/cache/store.rb +5 -3
  27. data/lib/rigor/cli/annotate_command.rb +122 -16
  28. data/lib/rigor/cli/baseline_command.rb +4 -3
  29. data/lib/rigor/cli/check_command.rb +118 -16
  30. data/lib/rigor/cli/coverage_command.rb +148 -16
  31. data/lib/rigor/cli/coverage_scan.rb +57 -0
  32. data/lib/rigor/cli/explain_command.rb +2 -0
  33. data/lib/rigor/cli/lsp_command.rb +3 -7
  34. data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
  35. data/lib/rigor/cli/mutation_protection_report.rb +73 -0
  36. data/lib/rigor/cli/options.rb +9 -0
  37. data/lib/rigor/cli/plugins_command.rb +4 -5
  38. data/lib/rigor/cli/plugins_renderer.rb +0 -2
  39. data/lib/rigor/cli/protection_renderer.rb +63 -0
  40. data/lib/rigor/cli/protection_report.rb +68 -0
  41. data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
  42. data/lib/rigor/cli/sig_gen_command.rb +2 -1
  43. data/lib/rigor/cli/trace_command.rb +2 -1
  44. data/lib/rigor/cli/triage_command.rb +8 -4
  45. data/lib/rigor/cli/triage_renderer.rb +15 -1
  46. data/lib/rigor/cli/type_of_command.rb +1 -1
  47. data/lib/rigor/cli/type_scan_command.rb +2 -1
  48. data/lib/rigor/cli.rb +12 -3
  49. data/lib/rigor/configuration/dependencies.rb +2 -4
  50. data/lib/rigor/configuration/severity_profile.rb +13 -1
  51. data/lib/rigor/configuration.rb +100 -6
  52. data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
  53. data/lib/rigor/environment/class_registry.rb +4 -3
  54. data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
  55. data/lib/rigor/environment/lockfile_resolver.rb +1 -1
  56. data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
  57. data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
  58. data/lib/rigor/environment/rbs_loader.rb +74 -5
  59. data/lib/rigor/environment.rb +17 -7
  60. data/lib/rigor/flow_contribution/fact.rb +1 -1
  61. data/lib/rigor/flow_contribution.rb +3 -5
  62. data/lib/rigor/inference/acceptance.rb +17 -9
  63. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  64. data/lib/rigor/inference/body_fixpoint.rb +89 -0
  65. data/lib/rigor/inference/budget_trace.rb +29 -2
  66. data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
  67. data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
  68. data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
  69. data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
  70. data/lib/rigor/inference/expression_typer.rb +1072 -71
  71. data/lib/rigor/inference/hkt_body.rb +8 -11
  72. data/lib/rigor/inference/hkt_body_parser.rb +10 -12
  73. data/lib/rigor/inference/hkt_registry.rb +10 -11
  74. data/lib/rigor/inference/macro_block_self_type.rb +2 -2
  75. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
  76. data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
  77. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +210 -35
  78. data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
  79. data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
  80. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
  81. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
  82. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
  83. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
  84. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
  85. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
  86. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
  87. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +237 -24
  88. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
  89. data/lib/rigor/inference/method_dispatcher.rb +112 -49
  90. data/lib/rigor/inference/method_parameter_binder.rb +56 -2
  91. data/lib/rigor/inference/multi_target_binder.rb +46 -3
  92. data/lib/rigor/inference/mutation_widening.rb +147 -11
  93. data/lib/rigor/inference/narrowing.rb +284 -53
  94. data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
  95. data/lib/rigor/inference/project_patched_methods.rb +4 -7
  96. data/lib/rigor/inference/project_patched_scanner.rb +2 -13
  97. data/lib/rigor/inference/protection_scanner.rb +86 -0
  98. data/lib/rigor/inference/scope_indexer.rb +821 -76
  99. data/lib/rigor/inference/statement_evaluator.rb +1179 -102
  100. data/lib/rigor/inference/struct_fold_safety.rb +181 -0
  101. data/lib/rigor/inference/synthetic_method.rb +7 -7
  102. data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
  103. data/lib/rigor/language_server/completion_provider.rb +6 -12
  104. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  105. data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
  106. data/lib/rigor/language_server/hover_provider.rb +2 -3
  107. data/lib/rigor/language_server/hover_renderer.rb +2 -11
  108. data/lib/rigor/language_server/server.rb +9 -17
  109. data/lib/rigor/language_server.rb +4 -5
  110. data/lib/rigor/plugin/base.rb +245 -87
  111. data/lib/rigor/plugin/macro/block_as_method.rb +25 -25
  112. data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
  113. data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
  114. data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
  115. data/lib/rigor/plugin/macro.rb +6 -8
  116. data/lib/rigor/plugin/manifest.rb +49 -90
  117. data/lib/rigor/plugin/node_rule_walk.rb +59 -14
  118. data/lib/rigor/plugin/registry.rb +18 -18
  119. data/lib/rigor/plugin/type_node_resolver.rb +6 -8
  120. data/lib/rigor/protection/mutation_scanner.rb +120 -0
  121. data/lib/rigor/protection/mutator.rb +246 -0
  122. data/lib/rigor/rbs_extended.rb +24 -36
  123. data/lib/rigor/reflection.rb +4 -7
  124. data/lib/rigor/scope/discovery_index.rb +16 -2
  125. data/lib/rigor/scope.rb +185 -16
  126. data/lib/rigor/sig_gen/generator.rb +8 -0
  127. data/lib/rigor/sig_gen/observed_call.rb +3 -3
  128. data/lib/rigor/sig_gen/writer.rb +40 -2
  129. data/lib/rigor/source/constant_path.rb +62 -0
  130. data/lib/rigor/source.rb +1 -0
  131. data/lib/rigor/triage/catalogue.rb +4 -19
  132. data/lib/rigor/triage.rb +69 -1
  133. data/lib/rigor/type/bound_method.rb +2 -11
  134. data/lib/rigor/type/combinator.rb +45 -3
  135. data/lib/rigor/type/constant.rb +2 -11
  136. data/lib/rigor/type/data_class.rb +2 -11
  137. data/lib/rigor/type/data_instance.rb +2 -11
  138. data/lib/rigor/type/hash_shape.rb +2 -11
  139. data/lib/rigor/type/integer_range.rb +2 -11
  140. data/lib/rigor/type/intersection.rb +2 -11
  141. data/lib/rigor/type/nominal.rb +2 -11
  142. data/lib/rigor/type/plain_lattice.rb +37 -0
  143. data/lib/rigor/type/refined.rb +72 -13
  144. data/lib/rigor/type/singleton.rb +2 -11
  145. data/lib/rigor/type/struct_class.rb +75 -0
  146. data/lib/rigor/type/struct_instance.rb +93 -0
  147. data/lib/rigor/type/tuple.rb +5 -15
  148. data/lib/rigor/type.rb +2 -0
  149. data/lib/rigor/version.rb +1 -1
  150. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
  151. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
  152. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +16 -32
  153. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
  154. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
  155. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
  156. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +34 -100
  157. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
  158. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
  159. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
  160. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +26 -27
  161. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
  162. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +9 -8
  163. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
  164. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
  165. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
  166. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
  167. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
  168. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
  169. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
  170. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +18 -49
  171. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
  172. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
  173. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +4 -4
  174. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
  175. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
  176. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
  177. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
  178. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +22 -35
  179. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
  180. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
  181. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +16 -23
  182. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
  183. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
  184. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
  185. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  186. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +21 -27
  187. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
  188. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
  189. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
  190. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
  191. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
  192. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
  193. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
  194. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
  195. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
  196. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
  197. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +52 -40
  198. data/sig/rigor/analysis/fact_store.rbs +3 -0
  199. data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
  200. data/sig/rigor/plugin/base.rbs +5 -2
  201. data/sig/rigor/plugin/manifest.rbs +1 -2
  202. data/sig/rigor/scope.rbs +18 -1
  203. data/sig/rigor/type.rbs +37 -1
  204. data/sig/rigor.rbs +1 -1
  205. data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
  206. data/skills/rigor-plugin-author/SKILL.md +6 -4
  207. data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
  208. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
  209. metadata +25 -2
  210. data/lib/rigor/plugin/macro/external_file.rb +0 -143
@@ -7,18 +7,15 @@ module Rigor
7
7
  # piece of a Rigor-side type expression that the reducer
8
8
  # ({HktReducer}) walks against a concrete argument list.
9
9
  #
10
- # Slice 2a ships a programmatic constructor surface only:
11
- # plugin and Rigor-bundled overlay authors build a body
12
- # tree by hand using these node types. The string-grammar
13
- # parser that reads `Definition#body` (the raw String slot
14
- # already populated by Slice 1's `HktDirectives.parse_define`)
15
- # into a tree is Slice 2b's deliverable; until it ships, the
16
- # `body` String stays opaque and `body_tree` is the
17
- # evaluable form.
10
+ # Slice 2a ships a programmatic constructor surface; plugin
11
+ # and Rigor-bundled overlay authors may build a body tree
12
+ # by hand using these node types. The string-grammar parser
13
+ # (`HktBodyParser`, Slice 2b, shipped) reads `Definition#body`
14
+ # (populated by Slice 1's `HktDirectives.parse_define`) into
15
+ # this node tree; `body_tree` is the evaluable form.
18
16
  #
19
- # The five node types cover the JSON.parse and dry-monads
20
- # use cases ADR-20 § Implementation slicing names as
21
- # near-term adopters:
17
+ # The nine node types cover JSON.parse, dry-monads, and the
18
+ # ADR-20 § D3 conditional / membership forms (shipped):
22
19
  #
23
20
  # - {TypeLeaf} — wraps a fully-built `Rigor::Type`
24
21
  # (use for atoms like `nil`, `Constant<true>`,
@@ -11,29 +11,27 @@ module Rigor
11
11
  # `%a{rigor:v1:hkt_define}` payloads) into the `HktBody`
12
12
  # node tree the Slice 2a reducer evaluates against.
13
13
  #
14
- # The minimum-viable grammar covered here is the
15
- # union-of-atoms-and-parameterised-forms subset of ADR-20
16
- # § D3 sufficient for `JSON.parse`'s `json::value`
17
- # recursive sum and for any other recursive-data-shape
18
- # signatures (Lisp value trees, dry-types refinements
19
- # without conditionals). The conditional / indexed-access
20
- # forms (`E <: T ? A : B`, `E in [k1, k2]`) drafted in D3
21
- # remain a follow-up slice — bodies that contain `?`
22
- # raise `ParseError` and the calling directive parser
23
- # drops the body_tree (the body String remains stored and
24
- # the reducer falls back to `app.bound`).
14
+ # The grammar implements the full ADR-20 § D3 subset:
15
+ # union-of-atoms/parameterised-forms for `JSON.parse`'s
16
+ # `json::value` recursive sum, plus the conditional and
17
+ # membership forms shipped in subsequent slices. Indexed-access
18
+ # forms remain deferred (no concrete demand yet).
25
19
  #
26
- # ## Grammar (slice 2b)
20
+ # ## Grammar
27
21
  #
28
22
  # body := union
29
23
  # union := type_expr ("|" type_expr)*
30
24
  # type_expr := atom | nominal_app | app_ref | param
25
+ # | conditional
31
26
  # atom := "nil" | "true" | "false" | "bool" | "untyped"
32
27
  # param := UCNAME (when UCNAME ∈ params)
33
28
  # nominal_app := class_name ("[" type_expr ("," type_expr)* "]")?
34
29
  # class_name := "::"? UCNAME ("::" UCNAME)*
35
30
  # app_ref := "App" "[" uri "," type_expr ("," type_expr)* "]"
36
31
  # uri := IDENT ("::" IDENT)+
32
+ # conditional := "(" test "?" union ":" union ")"
33
+ # test := type_expr ("<:" | "==") type_expr
34
+ # | type_expr "in" "[" type_expr ("," type_expr)* "]"
37
35
  # UCNAME := /[A-Z]\w*/
38
36
  # IDENT := /[a-z_]\w*/
39
37
  #
@@ -10,12 +10,12 @@ module Rigor
10
10
  # `%a{rigor:v1:hkt_define: ...}` annotations in shipped
11
11
  # `.rbs` files.
12
12
  #
13
- # Slice 1 keeps the registry **opaque**: it stores the
14
- # registration metadata (arity, variance, bound) and the
15
- # un-evaluated definition body (a raw String Slice 2
16
- # introduces the conditional / indexed-access evaluator that
17
- # parses the body and reduces `Type::App` instances against
18
- # it). The carrier never needs to read from the registry
13
+ # The registry stores registration metadata (arity, variance,
14
+ # bound) and the definition body as both a raw String and an
15
+ # evaluable `HktBody` node tree. `HktReducer` (Slice 2a) and
16
+ # `HktBodyParser` (Slice 2b) are both shipped and reduce
17
+ # `Type::App` instances against the definition. The carrier
18
+ # never needs to read from the registry
19
19
  # because Slice 1's `Type::App` carries its `bound` directly;
20
20
  # the registry exists at this slice solely so the parser
21
21
  # round-trip and downstream slices have a stable target API.
@@ -65,16 +65,15 @@ module Rigor
65
65
  # definition.
66
66
  #
67
67
  # `body` is the raw String payload from the `%a{...}`
68
- # annotation (Slice 1's parser populates it). It stays
69
- # opaque until Slice 2b's body-string parser lands.
68
+ # annotation (Slice 1's parser populates it); parsed into
69
+ # `body_tree` by `HktBodyParser` (Slice 2b, shipped).
70
70
  #
71
- # `body_tree` is the optional evaluable form: a
71
+ # `body_tree` is the evaluable form: a
72
72
  # `Rigor::Inference::HktBody::*` node tree the Slice 2a
73
73
  # reducer walks against the application's concrete
74
74
  # arguments. Plugin and Rigor-bundled overlay authors
75
75
  # construct it programmatically through
76
- # {with_body_tree}; the Slice 2b string parser will set
77
- # it from `body` once it ships. The reducer treats a
76
+ # {.definition_with_body_tree}. The reducer treats a
78
77
  # `nil` `body_tree` as "definition not yet evaluable"
79
78
  # and returns the registered bound.
80
79
  Definition = Data.define(:uri, :params, :body, :body_tree, :source_path, :source_line) do
@@ -17,7 +17,7 @@ module Rigor
17
17
  # `MyApp.get(...)` call);
18
18
  # - the underlying class `X` equals or inherits from the
19
19
  # entry's `receiver_constraint`;
20
- # - the call's method name is in the entry's `verbs`.
20
+ # - the call's method name is in the entry's `method_names`.
21
21
  #
22
22
  # On a match the helper returns the **instance** type of
23
23
  # the receiver class (`Nominal[X]`) — the narrowed
@@ -55,7 +55,7 @@ module Rigor
55
55
  # replaces the per-call plugins × block_as_methods linear scan.
56
56
  # Entries arrive in (plugin registration, declaration) order, so
57
57
  # the first ancestry match below is the same entry the previous
58
- # walk returned; the verb membership the old `matches?` checked
58
+ # walk returned; the method-name membership the old `matches?` checked
59
59
  # is guaranteed by the table key.
60
60
  entries = registry.contribution_index.block_entries_for(call_node.name)
61
61
  entries.each do |entry|
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # `Array#to_h { |x| [k, v] }` (and the no-block-pair tuple form's
9
+ # block sibling) return-type fold.
10
+ #
11
+ # `Enumerable#to_h` with a block maps every element to a
12
+ # `[key, value]` pair and collects them into a Hash. When the
13
+ # block's inferred return type is a recognizable 2-element
14
+ # `Tuple` (`[K, V]`), this tier projects the pair into a
15
+ # `Hash[K, V]` nominal whose key/value parameters are the
16
+ # widened pair types. Without this fold the call hits the RBS
17
+ # generic and the block's `[K, V]` return is dropped, typing as
18
+ # `Hash[Dynamic[top], Dynamic[top]]`.
19
+ #
20
+ # Value-pinned constants in the pair (`Constant[2]`) are widened
21
+ # to their nominal (`Integer`) for the Hash parameters: the built
22
+ # hash holds many keys, so pinning the parameter to a single
23
+ # element's literal would be unsound for the aggregate. The same
24
+ # widening the loop-body fixpoint applies (`Combinator#
25
+ # widen_value_pinned`) is reused.
26
+ #
27
+ # Declines (returns `nil`, leaving today's RBS answer) when:
28
+ #
29
+ # - there is no block at the call site,
30
+ # - the block return type is not a 2-element `Tuple`, or
31
+ # - the receiver is not an Array-shaped carrier (`Tuple` or
32
+ # `Array` nominal). Hash receivers keep their existing
33
+ # `ShapeDispatch#hash_to_h` identity fold.
34
+ module ArrayToHFolding
35
+ module_function
36
+
37
+ def try_dispatch(context)
38
+ return nil unless context.method_name == :to_h
39
+
40
+ block_type = context.block_type
41
+ return nil unless block_type.is_a?(Type::Tuple)
42
+ return nil unless block_type.elements.size == 2
43
+ return nil unless array_shaped?(context.receiver)
44
+
45
+ key = Type::Combinator.widen_value_pinned(block_type.elements[0])
46
+ value = Type::Combinator.widen_value_pinned(block_type.elements[1])
47
+ Type::Combinator.nominal_of("Hash", type_args: [key, value])
48
+ end
49
+
50
+ def array_shaped?(receiver)
51
+ case receiver
52
+ when Type::Tuple then true
53
+ when Type::Nominal then receiver.class_name == "Array"
54
+ else false
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -42,10 +42,7 @@ module Rigor
42
42
  #
43
43
  # This is the single place the call-context field list is
44
44
  # enumerated — the whole point of the value object is to absorb
45
- # the wide keyword list the tiers used to each redeclare. The
46
- # ParameterLists disable here retires the per-tier disables (the
47
- # `RbsDispatch` quartet-plus signatures) rather than adding to
48
- # them.
45
+ # the wide keyword list the tiers used to each redeclare.
49
46
  def self.build(receiver:, method_name:, args:, # rubocop:disable Metrics/ParameterLists
50
47
  block_type: nil, environment: nil, call_node: nil,
51
48
  scope: nil, self_type_override: nil, public_only: false)
@@ -40,7 +40,7 @@ module Rigor
40
40
  # receiver/argument combination.
41
41
  #
42
42
  # Anything else returns `nil`, signalling "no rule matched" so the
43
- # caller (`ExpressionTyper`) falls back to `Dynamic[Top]` and records a
43
+ # caller (`MethodDispatcher`) falls back to `Dynamic[Top]` and records a
44
44
  # fail-soft event. Slice 4 (RBS-backed) layers another dispatch tier
45
45
  # behind this rule book, but the constant-folding semantics defined
46
46
  # here MUST NOT regress: any value reachable by literal arithmetic at
@@ -51,7 +51,7 @@ module Rigor
51
51
  NUMERIC_BINARY = Set[
52
52
  :+, :-, :*, :/, :%, :**, :&, :|, :^, :<<, :>>,
53
53
  :<, :<=, :>, :>=, :==, :!=, :<=>,
54
- :gcd, :lcm, :fdiv
54
+ :gcd, :lcm, :fdiv, :quo, :ceildiv, :[]
55
55
  ].freeze
56
56
  STRING_BINARY = Set[
57
57
  :+, :*, :==, :!=, :<, :<=, :>, :>=, :<=>,
@@ -60,12 +60,31 @@ module Rigor
60
60
  :match?, :index, :rindex, :center, :ljust, :rjust,
61
61
  # 1-arg pure transforms/queries whose output never exceeds the
62
62
  # input: `delete`/`squeeze` shrink the string, `count` → Integer.
63
- :delete, :count, :squeeze
63
+ :delete, :count, :squeeze,
64
+ # ASCII / Unicode-case-fold comparison — deterministic, no
65
+ # locale read: `casecmp` → -1/0/1, `casecmp?` → bool/nil.
66
+ :casecmp, :casecmp?
64
67
  ].freeze
65
68
  SYMBOL_BINARY = Set[:==, :!=, :<=>, :<, :<=, :>, :>=].freeze
66
69
  BOOL_BINARY = Set[:&, :|, :^, :==, :!=, :===].freeze
67
70
  NIL_BINARY = Set[:==, :!=].freeze
68
- RATIONAL_BINARY = Set[:div, :modulo, :%, :remainder, :fdiv].freeze
71
+ # Rational arithmetic / ordering are exact and pure. Division
72
+ # (`/`) and `**` may return a `Float`/`Complex` for some operands,
73
+ # all of which are foldable `Constant` value classes. `==` / `!=`
74
+ # are deliberately EXCLUDED: `Rational#==` (`nurat_eqeq_p`) routes
75
+ # through `rb_funcall(:==)` on the operands — user-redefinable —
76
+ # so the catalog classifies it `:dispatch` and the equality stays
77
+ # the RBS `bool`. (The set would otherwise bypass that gate.)
78
+ RATIONAL_BINARY = Set[
79
+ :+, :-, :*, :/, :**, :<=>, :<, :<=, :>, :>=,
80
+ :div, :modulo, :%, :remainder, :fdiv, :quo
81
+ ].freeze
82
+ # Complex arithmetic. `ops_for` gains a `Complex` branch so these
83
+ # reach the binary fold path (Complex was previously unary-only).
84
+ # `/` and `**` stay foldable (Complex result). `==` / `!=` are
85
+ # excluded for the same reason as Rational (`nucomp_eqeq_p`
86
+ # delegates to operand `==`); ordering is undefined for Complex.
87
+ COMPLEX_BINARY = Set[:+, :-, :*, :/, :**].freeze
69
88
 
70
89
  # v0.0.3 C — pure unary catalogue. Each method must:
71
90
  # - take zero arguments,
@@ -83,20 +102,30 @@ module Rigor
83
102
  # user-defined `def is_odd(n) = n.odd?` so
84
103
  # `Parity.new.is_odd(3)` types as `Constant[true]`
85
104
  # rather than the RBS-widened `bool`.
105
+ # NOTE: `:hash` is deliberately NOT in any of these sets.
106
+ # `Object#hash` (and the `String`/`Symbol`/`Integer`/`Float`
107
+ # overrides) is salted with a per-process SipHash seed, so
108
+ # `"abc".hash` returns a different Integer in every Ruby
109
+ # process. Folding it to a `Constant` would bake one process's
110
+ # value into the type (and the on-disk cache), making the
111
+ # result non-deterministic across runs — a violation of the
112
+ # purity contract this catalogue rests on. A literal's `.hash`
113
+ # therefore stays the RBS-widened `Integer`. The deterministic
114
+ # siblings `:inspect` / `:to_s` remain folded.
86
115
  INTEGER_UNARY = Set[
87
116
  :odd?, :even?, :zero?, :positive?, :negative?,
88
117
  :succ, :pred, :next, :abs, :magnitude,
89
118
  :bit_length, :to_s, :to_i, :to_int, :to_f,
90
119
  :floor, :ceil, :round, :truncate, :chr,
91
- :inspect, :hash, :-@, :+@, :~
120
+ :inspect, :-@, :+@, :~, :to_r, :to_c
92
121
  ].freeze
93
122
  FLOAT_UNARY = Set[
94
123
  :zero?, :positive?, :negative?,
95
124
  :nan?, :finite?, :infinite?,
96
125
  :abs, :magnitude, :floor, :ceil, :round, :truncate,
97
126
  :next_float, :prev_float,
98
- :to_s, :to_i, :to_int, :to_f,
99
- :inspect, :hash, :-@, :+@
127
+ :to_s, :to_i, :to_int, :to_f, :to_r, :rationalize,
128
+ :inspect, :-@, :+@
100
129
  ].freeze
101
130
  STRING_UNARY = Set[
102
131
  :upcase, :downcase, :capitalize, :swapcase,
@@ -104,20 +133,29 @@ module Rigor
104
133
  :empty?, :strip, :lstrip, :rstrip, :chomp, :chop, :squeeze,
105
134
  :to_s, :to_str, :to_sym, :intern,
106
135
  :to_i, :to_f, :ord, :chr, :hex, :oct, :succ, :next,
107
- :inspect, :hash
136
+ :sum, :inspect
108
137
  ].freeze
109
138
  SYMBOL_UNARY = Set[
110
139
  :to_s, :to_sym, :to_proc, :length, :size,
111
140
  :empty?, :upcase, :downcase, :capitalize,
112
- :swapcase, :inspect, :hash
141
+ :swapcase, :succ, :next, :inspect
113
142
  ].freeze
114
- BOOL_UNARY = Set[:!, :to_s, :inspect, :hash, :&, :|, :^].freeze
115
- NIL_UNARY = Set[:nil?, :!, :to_s, :to_a, :to_h, :inspect, :hash].freeze
143
+ BOOL_UNARY = Set[:!, :to_s, :inspect, :&, :|, :^].freeze
144
+ NIL_UNARY = Set[:nil?, :!, :to_s, :to_a, :to_h, :inspect].freeze
116
145
  RATIONAL_UNARY = Set[
117
146
  :zero?, :integer?, :real, :abs2,
118
- :conj, :conjugate, :nonzero?
147
+ :conj, :conjugate, :nonzero?,
148
+ :numerator, :denominator, :abs, :magnitude,
149
+ :to_f, :to_i, :to_int, :to_r, :rationalize,
150
+ :floor, :ceil, :round, :truncate,
151
+ :-@, :+@
152
+ ].freeze
153
+ COMPLEX_UNARY = Set[
154
+ :zero?, :nonzero?,
155
+ :abs, :magnitude, :abs2, :arg, :angle, :phase,
156
+ :conjugate, :conj, :real, :imaginary, :imag,
157
+ :to_c, :-@, :+@
119
158
  ].freeze
120
- COMPLEX_UNARY = Set[:zero?, :nonzero?].freeze
121
159
 
122
160
  STRING_FOLD_BYTE_LIMIT = 4096
123
161
 
@@ -226,13 +264,54 @@ module Rigor
226
264
  case type
227
265
  when Type::Constant then [type.value]
228
266
  when Type::Union
229
- return nil unless type.members.all?(Type::Constant)
230
-
231
- type.members.map(&:value)
267
+ return type.members.map(&:value) if type.members.all?(Type::Constant)
268
+
269
+ # A union that mixes `Constant<Integer>` and `IntegerRange`
270
+ # members (e.g. an accumulator's running fixpoint assumption
271
+ # `1 | int<1, 6>`) folds as the bounding interval. The
272
+ # range-arithmetic path (`try_fold_binary_range`) then keeps
273
+ # the result an `IntegerRange` instead of bailing to Dynamic.
274
+ union_integer_bounds(type)
232
275
  when Type::IntegerRange then type
233
276
  end
234
277
  end
235
278
 
279
+ # Returns the bounding `IntegerRange` over a union whose members
280
+ # are each an Integer `Constant` or an `IntegerRange`; `nil`
281
+ # otherwise (a Float constant or any non-numeric member declines,
282
+ # so precision is never silently lost).
283
+ def union_integer_bounds(union)
284
+ lowers = []
285
+ uppers = []
286
+ union.members.each do |member|
287
+ case member
288
+ when Type::Constant
289
+ return nil unless member.value.is_a?(Integer)
290
+
291
+ lowers << member.value
292
+ uppers << member.value
293
+ when Type::IntegerRange
294
+ lowers << member.lower
295
+ uppers << member.upper
296
+ else
297
+ return nil
298
+ end
299
+ end
300
+ # `IntegerRange#lower`/`#upper` surface an unbounded edge as
301
+ # `±Float::INFINITY`; `integer_range` wants the `±∞` *sentinel*,
302
+ # so map the extremum back.
303
+ Type::Combinator.integer_range(infinity_to_sentinel(lowers.min),
304
+ infinity_to_sentinel(uppers.max))
305
+ end
306
+
307
+ def infinity_to_sentinel(bound)
308
+ case bound
309
+ when -Float::INFINITY then Type::IntegerRange::NEG_INFINITY
310
+ when Float::INFINITY then Type::IntegerRange::POS_INFINITY
311
+ else bound
312
+ end
313
+ end
314
+
236
315
  def try_fold_unary(set, method_name)
237
316
  case set
238
317
  when Array then try_fold_unary_set(set, method_name)
@@ -345,9 +424,15 @@ module Rigor
345
424
  # Only fires on a single-receiver Range with finite integer
346
425
  # endpoints; mixed unions fall through so the existing
347
426
  # union-of-Constants path keeps the rest of the arms.
348
- RANGE_FOLD_METHODS = Set[:to_a, :first, :last, :min, :max, :count, :size, :length, :entries, :minmax].freeze
427
+ RANGE_FOLD_METHODS = Set[:to_a, :first, :last, :min, :max, :count, :size, :length, :entries, :minmax,
428
+ :sum].freeze
429
+ # 1-arg head/tail projections on a `Constant<Range>`. `first(n)` /
430
+ # `take(n)` return the first `n` elements, `last(n)` the final `n` —
431
+ # each lifts to a per-position `Tuple[Constant[Integer]…]`. The
432
+ # no-arg `first` / `last` stay on the unary path (single Integer).
433
+ RANGE_FOLD_BINARY_METHODS = Set[:first, :last, :take].freeze
349
434
  RANGE_TO_A_LIMIT = 16
350
- private_constant :RANGE_FOLD_METHODS, :RANGE_TO_A_LIMIT
435
+ private_constant :RANGE_FOLD_METHODS, :RANGE_FOLD_BINARY_METHODS, :RANGE_TO_A_LIMIT
351
436
 
352
437
  def try_fold_range_constant_unary(receiver_values, method_name)
353
438
  return nil unless RANGE_FOLD_METHODS.include?(method_name)
@@ -367,6 +452,11 @@ module Rigor
367
452
  when :last, :max then range_endpoint_constant(range, :last)
368
453
  when :count, :size, :length then Type::Combinator.constant_of(range.to_a.size)
369
454
  when :minmax then range_minmax_tuple(range)
455
+ # `range.sum` is closed-form (Gauss) for an integer range, so a
456
+ # huge range still costs O(1) and yields a single Integer — no
457
+ # materialisation, no cap needed. Endless ranges are already
458
+ # excluded by the Integer-endpoint guard in the caller.
459
+ when :sum then Type::Combinator.constant_of(range.sum)
370
460
  end
371
461
  end
372
462
 
@@ -400,10 +490,46 @@ module Rigor
400
490
  )
401
491
  end
402
492
 
493
+ # `(1..10).first(3)` / `.take(3)` / `.last(3)` — the 1-arg head /
494
+ # tail forms. `first`/`last` already fold no-arg through the unary
495
+ # path; this is the n-arg sibling, mirroring the Tuple carrier's
496
+ # `first(n)`/`take(n)` handlers. Lifts to `Tuple[Constant…]`.
497
+ def try_fold_range_constant_binary(receiver_values, method_name, arg_values)
498
+ return nil unless RANGE_FOLD_BINARY_METHODS.include?(method_name)
499
+ return nil unless receiver_values.size == 1 && arg_values.size == 1
500
+
501
+ range = receiver_values.first
502
+ return nil unless range.is_a?(Range)
503
+ return nil unless range.begin.is_a?(Integer) && range.end.is_a?(Integer)
504
+
505
+ range_take_tuple(range, method_name, arg_values.first)
506
+ rescue StandardError
507
+ nil
508
+ end
509
+
510
+ def range_take_tuple(range, method_name, count)
511
+ return nil unless count.is_a?(Integer) && !count.negative?
512
+ # `first(n)`/`last(n)`/`take(n)` materialise at most `min(n, size)`
513
+ # elements; cap that count so a huge `n` (or range) never blows up
514
+ # the Constant. `Range#size` is O(1) for integer endpoints.
515
+ return nil if [count, range.size].min > RANGE_TO_A_LIMIT
516
+
517
+ values = method_name == :last ? range.last(count) : range.first(count)
518
+ return Type::Combinator.tuple_of if values.empty?
519
+
520
+ Type::Combinator.tuple_of(*values.map { |v| Type::Combinator.constant_of(v) })
521
+ end
522
+
403
523
  def try_fold_binary_set(receiver_values, method_name, arg_values)
524
+ range_lift = try_fold_range_constant_binary(receiver_values, method_name, arg_values)
525
+ return range_lift if range_lift
526
+
404
527
  string_lift = try_fold_string_array_binary(receiver_values, method_name, arg_values)
405
528
  return string_lift if string_lift
406
529
 
530
+ integer_lift = try_fold_integer_array_binary(receiver_values, method_name, arg_values)
531
+ return integer_lift if integer_lift
532
+
407
533
  pathname_lift = try_fold_pathname_binary(receiver_values, method_name, arg_values)
408
534
  return pathname_lift if pathname_lift
409
535
 
@@ -415,15 +541,20 @@ module Rigor
415
541
  end
416
542
  build_constant_type(results, source: receiver_values + arg_values)
417
543
  end
418
- # v0.0.7 — `Constant<String>#chars` / `bytes` / `lines` /
419
- # `split` (no-arg) return a Ruby Array of foldable
544
+ # v0.0.7 — `Constant<String>#chars` / `bytes` / `codepoints` /
545
+ # `lines` / `split` (no-arg) return a Ruby Array of foldable
420
546
  # scalars; `foldable_constant_value?` rejects Array
421
547
  # results, so the standard unary path declines. Lift the
422
548
  # Array to a per-position `Tuple[Constant…]` directly,
423
549
  # capped at `STRING_ARRAY_LIFT_LIMIT` to keep the result
424
- # bounded for long strings.
425
- STRING_ARRAY_UNARY_METHODS = Set[:chars, :bytes, :lines, :split].freeze
426
- STRING_ARRAY_BINARY_METHODS = Set[:split, :scan].freeze
550
+ # bounded for long strings. (`codepoints` yields per-character
551
+ # Integer codepoints, the sibling of the byte-valued `bytes`.)
552
+ STRING_ARRAY_UNARY_METHODS = Set[:chars, :bytes, :codepoints, :lines, :split].freeze
553
+ # `partition` / `rpartition` always return a fixed 3-element
554
+ # `[head, separator, tail]` Array whose members are substrings of
555
+ # the receiver (bounded by the input), so they lift to a precise
556
+ # 3-slot `Tuple[Constant…]`.
557
+ STRING_ARRAY_BINARY_METHODS = Set[:split, :scan, :partition, :rpartition].freeze
427
558
  STRING_ARRAY_LIFT_LIMIT = 32
428
559
  private_constant :STRING_ARRAY_UNARY_METHODS,
429
560
  :STRING_ARRAY_BINARY_METHODS,
@@ -453,6 +584,14 @@ module Rigor
453
584
  INTEGER_ARRAY_UNARY_METHODS = Set[:digits].freeze
454
585
  private_constant :INTEGER_ARRAY_UNARY_METHODS
455
586
 
587
+ # 1-arg Integer methods that return an Array of foldable
588
+ # Integers: `digits(base)` (base-n place values; raises on a
589
+ # negative receiver or base < 2 → declines) and `gcdlcm(other)`
590
+ # (the fixed `[gcd, lcm]` pair). Both are pure arithmetic; the
591
+ # result lifts to a `Tuple[Constant[Integer]…]`.
592
+ INTEGER_ARRAY_BINARY_METHODS = Set[:digits, :gcdlcm].freeze
593
+ private_constant :INTEGER_ARRAY_BINARY_METHODS
594
+
456
595
  # v0.0.7 — `Constant<Pathname>` delegates to a curated set
457
596
  # of pure path-manipulation methods. Pathname is immutable
458
597
  # in Ruby (per its docstring) and the catalog classifies
@@ -572,6 +711,25 @@ module Rigor
572
711
  nil
573
712
  end
574
713
 
714
+ # `Constant<Integer>#digits(base)` / `#gcdlcm(other)` — the
715
+ # 1-arg Array-returning Integer methods. `digits(base)` declines
716
+ # on a negative receiver (the unary path's guard); other domain
717
+ # errors (base < 2) raise and are rescued. `gcdlcm` is total over
718
+ # Integer args.
719
+ def try_fold_integer_array_binary(receiver_values, method_name, arg_values)
720
+ return nil unless INTEGER_ARRAY_BINARY_METHODS.include?(method_name)
721
+ return nil unless receiver_values.size == 1 && arg_values.size == 1
722
+
723
+ receiver = receiver_values.first
724
+ arg = arg_values.first
725
+ return nil unless receiver.is_a?(Integer) && arg.is_a?(Integer)
726
+ return nil if method_name == :digits && receiver.negative?
727
+
728
+ lift_array_result(receiver.public_send(method_name, arg))
729
+ rescue StandardError
730
+ nil
731
+ end
732
+
575
733
  # `Constant<Complex>#rect` / `#rectangular` — lifts `[real, imaginary]`
576
734
  # to `Tuple[Constant[re], Constant[im]]`. Both components are always
577
735
  # numeric (Integer or Float for literal complexes), so they satisfy
@@ -1265,17 +1423,16 @@ module Rigor
1265
1423
  end
1266
1424
  end
1267
1425
 
1268
- # `String#reverse` / `#swapcase` etc. produce a
1269
- # string the same size as the receiver; only the
1270
- # already-handled binary `:+` / `:*` paths can
1271
- # explode the output. No unary string method
1272
- # currently in the catalogue grows beyond the input
1273
- # size, so this hook is a no-op today — kept as a
1274
- # placeholder so future additions (e.g. `:succ` on
1275
- # very long strings) can be guarded without
1276
- # restructuring.
1277
- def string_unary_blow_up?(_receiver_value, _method_name)
1278
- false
1426
+ # `String#reverse` / `#swapcase` / `#succ` etc. produce a string
1427
+ # at least as large as the receiver. The binary `:+` / `:*` paths
1428
+ # have their own `string_blow_up?` output guard; this is the unary
1429
+ # analogue decline to fold a unary String op whose receiver is
1430
+ # already at or beyond `STRING_FOLD_BYTE_LIMIT`, since the folded
1431
+ # output would be just as large and constant-materialising it buys
1432
+ # no precision worth the bytes. Non-String receivers never blow up
1433
+ # through a unary op, so they pass.
1434
+ def string_unary_blow_up?(receiver_value, _method_name)
1435
+ receiver_value.is_a?(String) && receiver_value.bytesize >= STRING_FOLD_BYTE_LIMIT
1279
1436
  end
1280
1437
 
1281
1438
  # Scalar / String / Symbol values fold; everything
@@ -1294,7 +1451,24 @@ module Rigor
1294
1451
  private_constant :FOLDABLE_CONSTANT_CLASSES
1295
1452
 
1296
1453
  def foldable_constant_value?(value)
1297
- FOLDABLE_CONSTANT_CLASSES.any? { |klass| value.is_a?(klass) }
1454
+ return false unless FOLDABLE_CONSTANT_CLASSES.any? { |klass| value.is_a?(klass) }
1455
+
1456
+ # A NaN result (`0.0 / 0.0`, `Float::NAN`-propagating arithmetic,
1457
+ # or a NaN-bearing Complex) is non-reflexive under `==`, so a
1458
+ # `Constant[NaN]` would break the `==` / `eql?` / `hash` contract
1459
+ # `build_constant_type` relies on for union dedup. Decline the
1460
+ # fold and let the RBS tier answer with the widened class.
1461
+ return false if value.is_a?(Float) && value.nan?
1462
+ return false if value.is_a?(Complex) && complex_nan?(value)
1463
+
1464
+ true
1465
+ end
1466
+
1467
+ # True when either component of a Complex is NaN.
1468
+ def complex_nan?(value)
1469
+ real = value.real
1470
+ imag = value.imaginary
1471
+ (real.is_a?(Float) && real.nan?) || (imag.is_a?(Float) && imag.nan?)
1298
1472
  end
1299
1473
 
1300
1474
  def safe?(receiver_value, method_name, arg_value)
@@ -1315,6 +1489,7 @@ module Rigor
1315
1489
  when true, false then BOOL_BINARY
1316
1490
  when nil then NIL_BINARY
1317
1491
  when Rational then RATIONAL_BINARY
1492
+ when Complex then COMPLEX_BINARY
1318
1493
  else Set.new
1319
1494
  end
1320
1495
  end