rigortype 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +154 -33
  3. data/lib/rigor/analysis/check_rules.rb +10 -18
  4. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
  5. data/lib/rigor/analysis/dependency_source_inference/builder.rb +47 -21
  6. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  7. data/lib/rigor/analysis/dependency_source_inference/index.rb +32 -3
  8. data/lib/rigor/analysis/dependency_source_inference/walker.rb +1 -1
  9. data/lib/rigor/analysis/dependency_source_inference.rb +1 -0
  10. data/lib/rigor/analysis/diagnostic.rb +0 -2
  11. data/lib/rigor/analysis/fact_store.rb +26 -6
  12. data/lib/rigor/analysis/result.rb +11 -3
  13. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  14. data/lib/rigor/analysis/run_stats.rb +193 -0
  15. data/lib/rigor/analysis/runner.rb +498 -12
  16. data/lib/rigor/analysis/worker_session.rb +327 -0
  17. data/lib/rigor/builtins/imported_refinements.rb +364 -55
  18. data/lib/rigor/builtins/regex_refinement.rb +17 -12
  19. data/lib/rigor/cache/descriptor.rb +1 -1
  20. data/lib/rigor/cache/rbs_descriptor.rb +3 -1
  21. data/lib/rigor/cache/store.rb +39 -6
  22. data/lib/rigor/cli/diff_command.rb +1 -1
  23. data/lib/rigor/cli/sig_gen_command.rb +173 -0
  24. data/lib/rigor/cli/type_of_command.rb +1 -1
  25. data/lib/rigor/cli/type_scan_renderer.rb +1 -1
  26. data/lib/rigor/cli/type_scan_report.rb +2 -2
  27. data/lib/rigor/cli.rb +61 -3
  28. data/lib/rigor/configuration/dependencies.rb +2 -2
  29. data/lib/rigor/configuration.rb +131 -6
  30. data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
  31. data/lib/rigor/environment/class_registry.rb +12 -3
  32. data/lib/rigor/environment/lockfile_resolver.rb +125 -0
  33. data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
  34. data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
  35. data/lib/rigor/environment/rbs_loader.rb +194 -6
  36. data/lib/rigor/environment/reflection.rb +152 -0
  37. data/lib/rigor/environment.rb +109 -6
  38. data/lib/rigor/flow_contribution/conflict.rb +2 -2
  39. data/lib/rigor/flow_contribution/element.rb +1 -1
  40. data/lib/rigor/flow_contribution/fact.rb +1 -1
  41. data/lib/rigor/flow_contribution/merge_result.rb +1 -1
  42. data/lib/rigor/flow_contribution/merger.rb +3 -3
  43. data/lib/rigor/flow_contribution.rb +2 -2
  44. data/lib/rigor/inference/acceptance.rb +35 -1
  45. data/lib/rigor/inference/block_parameter_binder.rb +0 -2
  46. data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
  47. data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
  48. data/lib/rigor/inference/coverage_scanner.rb +1 -1
  49. data/lib/rigor/inference/expression_typer.rb +77 -11
  50. data/lib/rigor/inference/fallback.rb +1 -1
  51. data/lib/rigor/inference/macro_block_self_type.rb +96 -0
  52. data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
  53. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -41
  54. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
  55. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
  56. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
  57. data/lib/rigor/inference/method_dispatcher/method_folding.rb +135 -0
  58. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +7 -12
  59. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
  60. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -44
  61. data/lib/rigor/inference/method_dispatcher.rb +274 -5
  62. data/lib/rigor/inference/method_parameter_binder.rb +22 -14
  63. data/lib/rigor/inference/narrowing.rb +129 -12
  64. data/lib/rigor/inference/rbs_type_translator.rb +0 -2
  65. data/lib/rigor/inference/scope_indexer.rb +14 -9
  66. data/lib/rigor/inference/statement_evaluator.rb +7 -7
  67. data/lib/rigor/inference/synthetic_method.rb +86 -0
  68. data/lib/rigor/inference/synthetic_method_index.rb +82 -0
  69. data/lib/rigor/inference/synthetic_method_scanner.rb +521 -0
  70. data/lib/rigor/plugin/blueprint.rb +60 -0
  71. data/lib/rigor/plugin/io_boundary.rb +0 -2
  72. data/lib/rigor/plugin/loader.rb +5 -3
  73. data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
  74. data/lib/rigor/plugin/macro/external_file.rb +143 -0
  75. data/lib/rigor/plugin/macro/heredoc_template.rb +201 -0
  76. data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
  77. data/lib/rigor/plugin/macro.rb +31 -0
  78. data/lib/rigor/plugin/manifest.rb +102 -10
  79. data/lib/rigor/plugin/registry.rb +43 -2
  80. data/lib/rigor/plugin/services.rb +1 -1
  81. data/lib/rigor/plugin/type_node_resolver.rb +52 -0
  82. data/lib/rigor/plugin.rb +2 -0
  83. data/lib/rigor/rbs_extended/reporter.rb +91 -0
  84. data/lib/rigor/rbs_extended.rb +131 -32
  85. data/lib/rigor/scope.rb +25 -8
  86. data/lib/rigor/sig_gen/classification.rb +36 -0
  87. data/lib/rigor/sig_gen/generator.rb +1048 -0
  88. data/lib/rigor/sig_gen/layout_index.rb +108 -0
  89. data/lib/rigor/sig_gen/method_candidate.rb +62 -0
  90. data/lib/rigor/sig_gen/observation_collector.rb +391 -0
  91. data/lib/rigor/sig_gen/observed_call.rb +62 -0
  92. data/lib/rigor/sig_gen/path_mapper.rb +116 -0
  93. data/lib/rigor/sig_gen/renderer.rb +157 -0
  94. data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
  95. data/lib/rigor/sig_gen/write_result.rb +48 -0
  96. data/lib/rigor/sig_gen/writer.rb +530 -0
  97. data/lib/rigor/sig_gen.rb +25 -0
  98. data/lib/rigor/trinary.rb +15 -11
  99. data/lib/rigor/type/bot.rb +6 -3
  100. data/lib/rigor/type/bound_method.rb +79 -0
  101. data/lib/rigor/type/combinator.rb +207 -3
  102. data/lib/rigor/type/constant.rb +13 -0
  103. data/lib/rigor/type/hash_shape.rb +0 -2
  104. data/lib/rigor/type/integer_range.rb +7 -7
  105. data/lib/rigor/type/refined.rb +18 -12
  106. data/lib/rigor/type/top.rb +4 -3
  107. data/lib/rigor/type/union.rb +20 -1
  108. data/lib/rigor/type.rb +1 -0
  109. data/lib/rigor/type_node/generic.rb +68 -0
  110. data/lib/rigor/type_node/identifier.rb +38 -0
  111. data/lib/rigor/type_node/indexed_access.rb +41 -0
  112. data/lib/rigor/type_node/integer_literal.rb +29 -0
  113. data/lib/rigor/type_node/name_scope.rb +52 -0
  114. data/lib/rigor/type_node/resolver_chain.rb +56 -0
  115. data/lib/rigor/type_node/string_literal.rb +32 -0
  116. data/lib/rigor/type_node/symbol_literal.rb +28 -0
  117. data/lib/rigor/type_node/union.rb +42 -0
  118. data/lib/rigor/type_node.rb +29 -0
  119. data/lib/rigor/version.rb +1 -1
  120. data/lib/rigor.rb +2 -0
  121. data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
  122. data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
  123. data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
  124. data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
  125. data/sig/rigor/cli/diff_command.rbs +4 -0
  126. data/sig/rigor/cli/explain_command.rbs +4 -0
  127. data/sig/rigor/cli/sig_gen_command.rbs +4 -0
  128. data/sig/rigor/cli/type_scan_command.rbs +3 -0
  129. data/sig/rigor/environment.rbs +8 -2
  130. data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
  131. data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
  132. data/sig/rigor/inference/builtins.rbs +2 -0
  133. data/sig/rigor/plugin/access_denied_error.rbs +3 -0
  134. data/sig/rigor/plugin/base.rbs +6 -0
  135. data/sig/rigor/plugin/blueprint.rbs +7 -0
  136. data/sig/rigor/plugin/fact_store.rbs +11 -0
  137. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  138. data/sig/rigor/plugin/load_error.rbs +6 -0
  139. data/sig/rigor/plugin/loader.rbs +20 -0
  140. data/sig/rigor/plugin/manifest.rbs +9 -0
  141. data/sig/rigor/plugin/registry.rbs +16 -0
  142. data/sig/rigor/plugin/services.rbs +3 -0
  143. data/sig/rigor/plugin/trust_policy.rbs +4 -0
  144. data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
  145. data/sig/rigor/plugin.rbs +8 -0
  146. data/sig/rigor/scope.rbs +4 -2
  147. data/sig/rigor/type.rbs +28 -6
  148. data/sig/rigor.rbs +35 -2
  149. metadata +90 -1
@@ -27,12 +27,11 @@ module Rigor
27
27
  # - `a.downto(b) { |i| … }` yields the same domain `[b, a]`,
28
28
  # just iterated in reverse. Lower bound from the
29
29
  # argument, upper bound from the receiver.
30
- module IteratorDispatch # rubocop:disable Metrics/ModuleLength
30
+ module IteratorDispatch
31
31
  module_function
32
32
 
33
33
  # @return [Array<Rigor::Type>, nil] block-param types, or
34
34
  # nil to fall through to the next tier.
35
- # rubocop:disable Metrics/CyclomaticComplexity
36
35
  def block_param_types(receiver:, method_name:, args:)
37
36
  case method_name
38
37
  when :times then times_block_params(receiver)
@@ -45,7 +44,6 @@ module Rigor
45
44
  when :each_slice, :each_cons then slice_block_params(receiver)
46
45
  end
47
46
  end
48
- # rubocop:enable Metrics/CyclomaticComplexity
49
47
 
50
48
  def times_block_params(receiver)
51
49
  return nil unless integer_rooted?(receiver)
@@ -36,10 +36,10 @@ module Rigor
36
36
  # the result into a `Constant<Rational>` / `Constant<Complex>`.
37
37
  # The factory accepts the same shapes as Ruby:
38
38
  # `Rational(a)`, `Rational(a, b)`, `Complex(a)`, `Complex(a, b)`.
39
- NUMERIC_CONSTRUCTORS = {
40
- Rational: ->(*args) { Rational(*args) },
41
- Complex: ->(*args) { Complex(*args) }
42
- }.freeze
39
+ NUMERIC_CONSTRUCTORS = Ractor.make_shareable({
40
+ Rational: Ractor.make_shareable(->(*args) { Rational(*args) }),
41
+ Complex: Ractor.make_shareable(->(*args) { Complex(*args) })
42
+ })
43
43
  private_constant :NUMERIC_CONSTRUCTORS
44
44
 
45
45
  # `Kernel#Integer(s)` predicate-aware refinement set
@@ -74,7 +74,7 @@ module Rigor
74
74
  private_constant :CONCAT_METHODS, :FORMAT_METHODS,
75
75
  :LITERAL_PRESERVING_METHODS, :WIDTH_PADDING_METHODS
76
76
 
77
- def try_dispatch(receiver:, method_name:, args:, **) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
77
+ def try_dispatch(receiver:, method_name:, args:, **)
78
78
  return fold_array_join(receiver, args) if method_name == :join
79
79
  return fold_format(args) if FORMAT_METHODS.include?(method_name)
80
80
 
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # `Method` (and friends) precision tier.
9
+ #
10
+ # Two folds make a `Method` carrier round-trip with its
11
+ # binding visible:
12
+ #
13
+ # 1. **Forward** — `<receiver>.method(:sym)` (or
14
+ # `.method("sym")`) lifts to {Type::BoundMethod}
15
+ # carrying the receiver type AND the resolved Symbol.
16
+ # Calling with a non-literal symbol-shaped argument
17
+ # declines so the RBS tier still answers
18
+ # `Nominal[Method]`.
19
+ # 2. **Backward** — `Type::BoundMethod#call(...)` /
20
+ # `#()` (Prism lowers `.()` into a CallNode whose
21
+ # `name` is `:call`) / `#[](...)` substitutes the
22
+ # bound `(receiver_type, method_name)` and recurses
23
+ # back into `MethodDispatcher.dispatch`. The
24
+ # re-entrant call lets the substituted dispatch
25
+ # consume every tier the original call site would
26
+ # have — constant folding, shape dispatch, RBS,
27
+ # plugin contributions, etc. The original block_type
28
+ # / environment / call_node / scope are threaded
29
+ # through unchanged so capture-sensitive tiers (the
30
+ # block fold) keep working.
31
+ #
32
+ # Lives ABOVE the standard precision-tier chain so the
33
+ # RBS tier never sees a `BoundMethod` receiver — `Method`
34
+ # erasure means RBS would otherwise return
35
+ # `Method#call: (*untyped) -> untyped`, which is exactly
36
+ # the precision loss the carrier exists to avoid.
37
+ module MethodFolding
38
+ module_function
39
+
40
+ # Forward fold. Returns a {Type::BoundMethod} when the
41
+ # call shape is `<receiver>.method(:name)` /
42
+ # `.method("name")` with a precisely-known Symbol /
43
+ # String argument. Declines on every other shape so
44
+ # the RBS tier still answers `Method` for non-folding
45
+ # cases.
46
+ #
47
+ # @param receiver [Rigor::Type] caller's receiver
48
+ # @param method_name [Symbol] the method being
49
+ # dispatched on `receiver` — only `:method` triggers
50
+ # the fold.
51
+ # @param args [Array<Rigor::Type>] caller's argument
52
+ # types in order. Only the single-argument case
53
+ # matches; other arities decline.
54
+ def try_forward(receiver:, method_name:, args:)
55
+ return nil unless method_name == :method
56
+ return nil if args.size != 1
57
+
58
+ bound_name = symbol_name_of(args.first)
59
+ return nil if bound_name.nil?
60
+
61
+ Type::Combinator.bound_method_of(receiver, bound_name)
62
+ end
63
+
64
+ # Backward fold. Recurses into `MethodDispatcher.dispatch`
65
+ # with the bound `(receiver_type, method_name)`. The
66
+ # `block_type` / `environment` / `call_node` / `scope`
67
+ # are forwarded so every downstream tier (constant
68
+ # folding, shape dispatch, plugin contributions, …)
69
+ # keeps the original call site's context. Returns
70
+ # `Dynamic[top]` rather than `nil` when the recursive
71
+ # dispatch declines so the call site still ends in a
72
+ # well-defined type (the gradual-safety net mirrors
73
+ # the engine's "BoundMethod erases to `Method`,
74
+ # `Method#call: (*untyped) -> untyped`" RBS fallback).
75
+ def try_backward(receiver:, method_name:, args:, block_type:, environment:, call_node:, scope:)
76
+ return nil unless receiver.is_a?(Type::BoundMethod)
77
+ return nil unless backward_method?(method_name)
78
+
79
+ # `Method#curry` is treated as identity on the carrier
80
+ # — `<bound>.curry` keeps the same
81
+ # `(receiver_type, method_name)` so a subsequent
82
+ # `<curried>.call` still routes through the recursive
83
+ # dispatch below. This is correct for the dominant
84
+ # no-arg form (`.curry.call`); partially-applied
85
+ # forms (`.curry(n).call(a)`) lose precision and fall
86
+ # through to RBS via the trailing
87
+ # `Type::Combinator.untyped`. A faithful
88
+ # `Type::CurriedBoundMethod(receiver_type,
89
+ # method_name, accumulated_args)` carrier is reserved
90
+ # for a future slice when concrete user demand
91
+ # surfaces.
92
+ return receiver if method_name == :curry
93
+
94
+ MethodDispatcher.dispatch(
95
+ receiver_type: receiver.receiver_type,
96
+ method_name: receiver.method_name,
97
+ arg_types: args,
98
+ block_type: block_type,
99
+ environment: environment,
100
+ call_node: call_node,
101
+ scope: scope
102
+ ) || Type::Combinator.untyped
103
+ end
104
+ # `Method#call` / `Method#()` and `Method#[]` are the
105
+ # invocation entry points on the `Method` API; the
106
+ # alias `===` is also `call` semantically but is more
107
+ # commonly used as a case-equality predicate, so we
108
+ # do NOT fold through it (the case/when narrowing path
109
+ # already special-cases `===` for branch typing).
110
+ # `Method#curry` rides through as identity (see the
111
+ # comment in `try_backward`).
112
+ BACKWARD_METHOD_NAMES = %i[call [] curry].freeze
113
+ private_constant :BACKWARD_METHOD_NAMES
114
+
115
+ def backward_method?(method_name)
116
+ BACKWARD_METHOD_NAMES.include?(method_name)
117
+ end
118
+
119
+ # `Object#method` accepts both Symbol and String at
120
+ # runtime (the latter coerced via `to_sym`). The
121
+ # `Constant<String>` form is rare in production code
122
+ # but cheap to support and matches Ruby's documented
123
+ # contract.
124
+ def symbol_name_of(arg)
125
+ return nil unless arg.is_a?(Type::Constant)
126
+
127
+ case arg.value
128
+ when Symbol then arg.value
129
+ when String then arg.value.to_sym
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -26,7 +26,7 @@ module Rigor
26
26
  # `Array#[](Range) -> Array[Elem]?` overload for a Range
27
27
  # argument. (Surfaced during v0.1.1 self-analysis; see the
28
28
  # "Interface-strictness on overload selection" item in
29
- # `docs/MILESTONES.md`.)
29
+ # `docs/ROADMAP.md`.)
30
30
  # 3. **Pass 2 — gradual fall-back.** If no fully strict overload
31
31
  # matches, accept the first arity-and-gradual-accept match
32
32
  # (the v0.1.1 behaviour). Alias / Interface / Intersection
@@ -41,7 +41,7 @@ module Rigor
41
41
  # (instance vs singleton). Both kinds share the same arity and
42
42
  # acceptance shape; the difference is only in which `Definition`
43
43
  # the caller fetched.
44
- module OverloadSelector # rubocop:disable Metrics/ModuleLength
44
+ module OverloadSelector
45
45
  module_function
46
46
 
47
47
  # @param method_definition [RBS::Definition::Method]
@@ -64,8 +64,8 @@ module Rigor
64
64
  # back to the first declaration.
65
65
  # @return [RBS::MethodType, nil] the chosen overload, or nil
66
66
  # when the definition has no method types at all.
67
- # rubocop:disable Metrics/ParameterLists
68
- def select(method_definition, arg_types:, self_type:, instance_type:, type_vars: {}, block_required: false)
67
+ def select(method_definition, arg_types:, self_type:, instance_type:, type_vars: {}, block_required: false,
68
+ environment: nil)
69
69
  overloads = method_definition.method_types
70
70
  return nil if overloads.empty?
71
71
 
@@ -75,7 +75,7 @@ module Rigor
75
75
  # `accepts_param?` so overload selection sees the
76
76
  # tighter type when filtering candidates by argument
77
77
  # compatibility.
78
- param_overrides = RbsExtended.param_type_override_map(method_definition)
78
+ param_overrides = RbsExtended.param_type_override_map(method_definition, environment: environment)
79
79
 
80
80
  # Pass 1: prefer overloads whose param types stay strict —
81
81
  # no translator-induced `Dynamic[Top]` from Alias /
@@ -113,7 +113,6 @@ module Rigor
113
113
 
114
114
  overloads.first
115
115
  end
116
- # rubocop:enable Metrics/ParameterLists
117
116
 
118
117
  def overload_has_block?(method_type)
119
118
  method_type.respond_to?(:block) && method_type.block
@@ -122,7 +121,7 @@ module Rigor
122
121
  class << self
123
122
  private
124
123
 
125
- # rubocop:disable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
124
+ # rubocop:disable Metrics/ParameterLists
126
125
  def find_matching_overload(overloads, arg_types:, self_type:, instance_type:, type_vars:, block_required:,
127
126
  param_overrides:, strict:)
128
127
  return nil if strict && arg_types.any? { |t| untyped_arg?(t) }
@@ -141,7 +140,7 @@ module Rigor
141
140
  )
142
141
  end
143
142
  end
144
- # rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
143
+ # rubocop:enable Metrics/ParameterLists
145
144
 
146
145
  # Treats the literal `untyped` carrier (`Dynamic[Top]`)
147
146
  # as too imprecise to drive a strict-pass match. Other
@@ -194,7 +193,6 @@ module Rigor
194
193
  end
195
194
  end
196
195
 
197
- # rubocop:disable Metrics/ParameterLists
198
196
  def matches?(method_type, arg_types, self_type:, instance_type:, type_vars:, param_overrides:)
199
197
  return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
200
198
 
@@ -213,7 +211,6 @@ module Rigor
213
211
  )
214
212
  end
215
213
  end
216
- # rubocop:enable Metrics/ParameterLists
217
214
 
218
215
  # Slice 4 phase 2c does not pass keyword arguments through the
219
216
  # call site (caller passes only positional `arg_types`). An
@@ -258,7 +255,6 @@ module Rigor
258
255
  head
259
256
  end
260
257
 
261
- # rubocop:disable Metrics/ParameterLists
262
258
  def accepts_param?(param, arg, self_type:, instance_type:, type_vars:, param_overrides:)
263
259
  param_type = param_overrides[param.name] || RbsTypeTranslator.translate(
264
260
  param.type,
@@ -269,7 +265,6 @@ module Rigor
269
265
  result = param_type.accepts(arg, mode: :gradual)
270
266
  result.yes? || result.maybe?
271
267
  end
272
- # rubocop:enable Metrics/ParameterLists
273
268
  end
274
269
  end
275
270
  end
@@ -162,7 +162,8 @@ module Rigor
162
162
  kind: kind,
163
163
  args: args,
164
164
  type_vars: type_vars,
165
- block_type: block_type
165
+ block_type: block_type,
166
+ environment: environment
166
167
  )
167
168
  rescue StandardError
168
169
  # Defensive: if RBS' definition builder raises on a broken
@@ -194,6 +195,18 @@ module Rigor
194
195
  ["Array", :instance, tuple_type_args(receiver)]
195
196
  when Type::HashShape
196
197
  ["Hash", :instance, hash_shape_type_args(receiver)]
198
+ when Type::BoundMethod
199
+ # `BoundMethod` is a precision-bearing alias for
200
+ # `Nominal[Method]`: it carries the
201
+ # `(receiver, method_name)` binding that
202
+ # `MethodFolding.try_backward` consumes at
203
+ # `.call` / `.()` / `[]`, but every other call
204
+ # site (`.owner` / `.name` / `.arity` / …) must
205
+ # still resolve through Method's RBS contract.
206
+ # Routing here keeps reflective Method methods
207
+ # working without forcing the carrier to
208
+ # collapse to a plain Nominal at construction.
209
+ ["Method", :instance, []]
197
210
  when Type::Dynamic
198
211
  receiver_descriptor(receiver.static_facet)
199
212
  end
@@ -241,15 +254,15 @@ module Rigor
241
254
  param_names.zip(receiver_args).to_h
242
255
  end
243
256
 
244
- # rubocop:disable Metrics/ParameterLists
245
- def translate_return_type(method_definition, class_name:, kind:, args:, type_vars:, block_type:)
257
+ def translate_return_type(method_definition, class_name:, kind:, args:, type_vars:, block_type:,
258
+ environment: nil)
246
259
  # Slice 4b-3 (ADR-7 § "Slice 4-A/4-B") — read the
247
260
  # return-type override through the merger so future
248
261
  # plugin / `:rbs_extended` bundles that also assert a
249
262
  # `return_type` slot at this call site compose with
250
263
  # the RBS::Extended directive instead of silently
251
264
  # racing it.
252
- override = merged_return_type(method_definition)
265
+ override = merged_return_type(method_definition, environment: environment)
253
266
  return override if override
254
267
 
255
268
  instance_type = Type::Combinator.nominal_of(class_name)
@@ -265,7 +278,8 @@ module Rigor
265
278
  self_type: self_type,
266
279
  instance_type: instance_type,
267
280
  type_vars: type_vars,
268
- block_required: !block_type.nil?
281
+ block_required: !block_type.nil?,
282
+ environment: environment
269
283
  )
270
284
  return nil unless method_type
271
285
 
@@ -278,7 +292,6 @@ module Rigor
278
292
  type_vars: full_type_vars
279
293
  )
280
294
  end
281
- # rubocop:enable Metrics/ParameterLists
282
295
 
283
296
  # ADR-7 § "Slice 4-A/4-B" — folds the
284
297
  # `RBS::Extended` `return:` directive (and any
@@ -287,8 +300,8 @@ module Rigor
287
300
  # before consuming. Returns the merged return type
288
301
  # or nil when no contribution overrides the
289
302
  # RBS-declared return.
290
- def merged_return_type(method_definition)
291
- contribution = RbsExtended.read_flow_contribution(method_definition)
303
+ def merged_return_type(method_definition, environment: nil)
304
+ contribution = RbsExtended.read_flow_contribution(method_definition, environment: environment)
292
305
  return nil if contribution.nil?
293
306
 
294
307
  Rigor::FlowContribution::Merger.merge([contribution]).return_type
@@ -377,13 +390,15 @@ module Rigor
377
390
  class_name: class_name,
378
391
  kind: kind,
379
392
  args: args,
380
- type_vars: type_vars
393
+ type_vars: type_vars,
394
+ environment: environment
381
395
  )
382
396
  rescue StandardError
383
397
  []
384
398
  end
385
399
 
386
- def extract_block_param_types(method_definition, class_name:, kind:, args:, type_vars:)
400
+ def extract_block_param_types(method_definition, class_name:, kind:, args:, type_vars:,
401
+ environment: nil)
387
402
  instance_type = Type::Combinator.nominal_of(class_name)
388
403
  self_type =
389
404
  case kind
@@ -397,7 +412,8 @@ module Rigor
397
412
  self_type: self_type,
398
413
  instance_type: instance_type,
399
414
  type_vars: type_vars,
400
- block_required: true
415
+ block_required: true,
416
+ environment: environment
401
417
  )
402
418
  return [] unless method_type
403
419
 
@@ -155,13 +155,13 @@ module Rigor
155
155
  # tier ahead of RBS sees the more precise carrier so
156
156
  # downstream narrowing (`if size > 0; …`) actually has a
157
157
  # range to intersect with.
158
- SIZE_RETURNING_NOMINALS = {
159
- "Array" => %i[size length count],
160
- "String" => %i[length size bytesize],
161
- "Hash" => %i[size length count],
162
- "Set" => %i[size length count],
163
- "Range" => %i[size length count]
164
- }.freeze
158
+ SIZE_RETURNING_NOMINALS = Ractor.make_shareable({
159
+ "Array" => %i[size length count],
160
+ "String" => %i[length size bytesize],
161
+ "Hash" => %i[size length count],
162
+ "Set" => %i[size length count],
163
+ "Range" => %i[size length count]
164
+ })
165
165
  private_constant :SIZE_RETURNING_NOMINALS
166
166
 
167
167
  # When the difference removes the empty value of the
@@ -323,39 +323,45 @@ module Rigor
323
323
  # `dispatch_nominal_size` so size-returning calls on
324
324
  # a `Refined[String, *]` still tighten to
325
325
  # `non_negative_int`.
326
- REFINED_STRING_PROJECTIONS = {
327
- %i[lowercase downcase] => :refined_self,
328
- %i[lowercase upcase] => :uppercase_string,
329
- %i[uppercase upcase] => :refined_self,
330
- %i[uppercase downcase] => :lowercase_string,
331
- %i[numeric downcase] => :refined_self,
332
- %i[numeric upcase] => :refined_self,
333
- # Digit-only strings are case-invariant; the prefix
334
- # letters in `0o…` / `0x…` are accepted by the
335
- # predicate in either case so the predicate-subset
336
- # is preserved across `#downcase` / `#upcase` even
337
- # though the value-set element changes.
338
- %i[decimal_int downcase] => :refined_self,
339
- %i[decimal_int upcase] => :refined_self,
340
- %i[octal_int downcase] => :refined_self,
341
- %i[octal_int upcase] => :refined_self,
342
- %i[hex_int downcase] => :refined_self,
343
- %i[hex_int upcase] => :refined_self,
344
- # v0.1.1 Track 1 slice 2 — `to_i` / `to_int` on a
345
- # known digit-only string. `decimal-int-string`
346
- # (`/\A\d+\z/`) and `numeric-string` (Rigor's
347
- # numeric-string predicate, ASCII digits) are
348
- # predicates over digit-only strings, so the parse
349
- # is total over the carrier domain and the result
350
- # is always `>= 0`. `non-negative-int` is the
351
- # tightest carrier that captures both the lower
352
- # bound and the integer-ness without inventing a
353
- # narrower carrier.
354
- %i[decimal_int to_i] => :non_negative_int,
355
- %i[decimal_int to_int] => :non_negative_int,
356
- %i[numeric to_i] => :non_negative_int,
357
- %i[numeric to_int] => :non_negative_int
358
- }.freeze
326
+ # ADR-15 Phase 4b.x — `Ractor.make_shareable` (not `.freeze`)
327
+ # because the keys are two-element Symbol arrays whose
328
+ # inner arrays are unfrozen under shallow `.freeze`.
329
+ # Surfaced on Discourse via `Ractor::IsolationError` when
330
+ # the dispatch loop's `REFINED_STRING_PROJECTIONS[[id, sym]]`
331
+ # lookup ran from a worker Ractor.
332
+ REFINED_STRING_PROJECTIONS = Ractor.make_shareable({
333
+ %i[lowercase downcase] => :refined_self,
334
+ %i[lowercase upcase] => :uppercase_string,
335
+ %i[uppercase upcase] => :refined_self,
336
+ %i[uppercase downcase] => :lowercase_string,
337
+ %i[numeric downcase] => :refined_self,
338
+ %i[numeric upcase] => :refined_self,
339
+ # Digit-only strings are case-invariant; the prefix
340
+ # letters in `0o…` / `0x…` are accepted by the
341
+ # predicate in either case so the predicate-subset
342
+ # is preserved across `#downcase` / `#upcase` even
343
+ # though the value-set element changes.
344
+ %i[decimal_int downcase] => :refined_self,
345
+ %i[decimal_int upcase] => :refined_self,
346
+ %i[octal_int downcase] => :refined_self,
347
+ %i[octal_int upcase] => :refined_self,
348
+ %i[hex_int downcase] => :refined_self,
349
+ %i[hex_int upcase] => :refined_self,
350
+ # v0.1.1 Track 1 slice 2 — `to_i` / `to_int` on a
351
+ # known digit-only string. `decimal-int-string`
352
+ # (`/\A\d+\z/`) and `numeric-string` (Rigor's
353
+ # numeric-string predicate, ASCII digits) are
354
+ # predicates over digit-only strings, so the parse
355
+ # is total over the carrier domain and the result
356
+ # is always `>= 0`. `non-negative-int` is the
357
+ # tightest carrier that captures both the lower
358
+ # bound and the integer-ness without inventing a
359
+ # narrower carrier.
360
+ %i[decimal_int to_i] => :non_negative_int,
361
+ %i[decimal_int to_int] => :non_negative_int,
362
+ %i[numeric to_i] => :non_negative_int,
363
+ %i[numeric to_int] => :non_negative_int
364
+ })
359
365
  private_constant :REFINED_STRING_PROJECTIONS
360
366
 
361
367
  def dispatch_refined(refined, method_name, args)
@@ -626,7 +632,6 @@ module Rigor
626
632
  # (so it can serve as a Hash key). Produces a closed
627
633
  # `HashShape` whose entries mirror the per-position
628
634
  # pairs. Empty Tuples fold to the empty HashShape.
629
- # rubocop:disable Metrics/CyclomaticComplexity
630
635
  def tuple_to_h(tuple, _method_name, args)
631
636
  return nil unless args.empty?
632
637
  return Type::Combinator.hash_shape_of({}) if tuple.elements.empty?
@@ -637,7 +642,6 @@ module Rigor
637
642
 
638
643
  Type::Combinator.hash_shape_of(pairs.to_h)
639
644
  end
640
- # rubocop:enable Metrics/CyclomaticComplexity
641
645
 
642
646
  def tuple_to_h_pair(element)
643
647
  return nil unless element.is_a?(Type::Tuple)
@@ -865,7 +869,6 @@ module Rigor
865
869
  # `HashShape` accepts as keys). Duplicate values would
866
870
  # alias under inversion, so Rigor declines on
867
871
  # collisions rather than silently dropping entries.
868
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
869
872
  def hash_invert(shape, _method_name, args)
870
873
  return nil unless args.empty?
871
874
  return nil unless shape.closed?
@@ -880,7 +883,6 @@ module Rigor
880
883
  end
881
884
  Type::Combinator.hash_shape_of(inverted)
882
885
  end
883
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
884
886
 
885
887
  # `shape.first` — returns the first `[k, v]` pair as a
886
888
  # 2-Tuple, or `Constant[nil]` when the shape is empty.