rigortype 0.3.6 → 0.3.7

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 (213) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/builtins/ruby_core/date.yml +6 -3
  4. data/data/builtins/ruby_core/enumerable.yml +1 -0
  5. data/data/builtins/ruby_core/exception.yml +2 -1
  6. data/data/builtins/ruby_core/file.yml +28 -14
  7. data/data/builtins/ruby_core/hash.yml +5 -2
  8. data/data/builtins/ruby_core/io.yml +33 -16
  9. data/data/builtins/ruby_core/random.yml +2 -1
  10. data/data/builtins/ruby_core/re.yml +6 -3
  11. data/data/builtins/ruby_core/struct.yml +2 -0
  12. data/data/builtins/ruby_core/time.yml +2 -1
  13. data/data/gem_overlay/activesupport/core_ext.rbs +409 -11
  14. data/docs/manual/02-cli-reference.md +16 -7
  15. data/docs/manual/05-inspecting-types.md +20 -8
  16. data/docs/manual/07-plugins.md +4 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +61 -0
  18. data/docs/manual/plugins/rigor-activerecord.md +74 -1
  19. data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
  20. data/docs/manual/plugins/rigor-sidekiq.md +22 -0
  21. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
  22. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
  24. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
  25. data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
  26. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
  28. data/lib/rigor/analysis/check_rules.rb +383 -48
  29. data/lib/rigor/analysis/crash_signature.rb +116 -0
  30. data/lib/rigor/analysis/dependency_recorder.rb +18 -3
  31. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  32. data/lib/rigor/analysis/incremental.rb +28 -0
  33. data/lib/rigor/analysis/incremental_session.rb +44 -9
  34. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
  35. data/lib/rigor/analysis/reachability/graph.rb +14 -5
  36. data/lib/rigor/analysis/reachability/scan.rb +7 -3
  37. data/lib/rigor/analysis/result.rb +32 -0
  38. data/lib/rigor/analysis/run_cache_key.rb +41 -1
  39. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
  40. data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
  41. data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
  42. data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
  43. data/lib/rigor/analysis/runner.rb +147 -10
  44. data/lib/rigor/analysis/worker_session.rb +10 -1
  45. data/lib/rigor/bleeding_edge.rb +7 -6
  46. data/lib/rigor/builtins/hkt_builtins.rb +45 -6
  47. data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
  48. data/lib/rigor/builtins/static_return_refinements.rb +20 -1
  49. data/lib/rigor/cache/descriptor.rb +42 -2
  50. data/lib/rigor/cache/incremental_snapshot.rb +40 -3
  51. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
  52. data/lib/rigor/cache/store.rb +10 -1
  53. data/lib/rigor/cli/coverage_command.rb +23 -18
  54. data/lib/rigor/cli/coverage_mutation.rb +13 -17
  55. data/lib/rigor/cli/coverage_scan.rb +47 -6
  56. data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
  57. data/lib/rigor/cli/fused_protection_report.rb +23 -2
  58. data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
  59. data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
  60. data/lib/rigor/cli/mutation_protection_report.rb +31 -2
  61. data/lib/rigor/cli/sig_gen_command.rb +18 -0
  62. data/lib/rigor/cli/type_of_command.rb +188 -41
  63. data/lib/rigor/cli/type_of_renderer.rb +72 -3
  64. data/lib/rigor/cli/type_scan_command.rb +10 -1
  65. data/lib/rigor/cli.rb +1 -1
  66. data/lib/rigor/configuration.rb +7 -4
  67. data/lib/rigor/effects/plugin_facts.rb +1 -1
  68. data/lib/rigor/effects/scanner.rb +2 -3
  69. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
  70. data/lib/rigor/environment/lockfile_resolver.rb +15 -5
  71. data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
  72. data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
  73. data/lib/rigor/environment/rbs_loader.rb +525 -46
  74. data/lib/rigor/environment.rb +38 -15
  75. data/lib/rigor/inference/acceptance.rb +172 -20
  76. data/lib/rigor/inference/captured_locals.rb +62 -0
  77. data/lib/rigor/inference/content_join.rb +347 -0
  78. data/lib/rigor/inference/def_handle.rb +15 -3
  79. data/lib/rigor/inference/def_node_resolver.rb +62 -3
  80. data/lib/rigor/inference/expression_typer.rb +1020 -141
  81. data/lib/rigor/inference/fork_map.rb +6 -1
  82. data/lib/rigor/inference/hkt_reducer.rb +12 -1
  83. data/lib/rigor/inference/hkt_registry.rb +46 -0
  84. data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
  85. data/lib/rigor/inference/index_write_widening.rb +48 -0
  86. data/lib/rigor/inference/indexed_narrowing.rb +14 -0
  87. data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
  88. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
  89. data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
  90. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
  91. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
  92. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
  93. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
  94. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
  95. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
  96. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
  97. data/lib/rigor/inference/method_dispatcher.rb +155 -14
  98. data/lib/rigor/inference/method_parameter_binder.rb +3 -3
  99. data/lib/rigor/inference/mutation_widening.rb +244 -174
  100. data/lib/rigor/inference/narrowing.rb +331 -27
  101. data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
  102. data/lib/rigor/inference/pre_eval_constants.rb +15 -0
  103. data/lib/rigor/inference/precision_scanner.rb +5 -2
  104. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  105. data/lib/rigor/inference/rbs_type_translator.rb +89 -39
  106. data/lib/rigor/inference/scope_indexer.rb +1493 -140
  107. data/lib/rigor/inference/statement_evaluator.rb +276 -91
  108. data/lib/rigor/inference/struct_fold_safety.rb +186 -27
  109. data/lib/rigor/inference/version_guard.rb +229 -0
  110. data/lib/rigor/language_server/selection_range_provider.rb +1 -1
  111. data/lib/rigor/plugin/base.rb +3 -2
  112. data/lib/rigor/plugin/inflector.rb +14 -5
  113. data/lib/rigor/plugin/io_boundary.rb +107 -4
  114. data/lib/rigor/plugin/loader.rb +14 -0
  115. data/lib/rigor/protection/analysis_guard.rb +60 -0
  116. data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
  117. data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
  118. data/lib/rigor/protection/discovery_seed.rb +1 -1
  119. data/lib/rigor/protection/measurement_integrity.rb +36 -0
  120. data/lib/rigor/protection/mutation_scanner.rb +61 -6
  121. data/lib/rigor/protection/mutator.rb +121 -18
  122. data/lib/rigor/reflection.rb +182 -30
  123. data/lib/rigor/scope/discovery_index.rb +39 -1
  124. data/lib/rigor/scope.rb +385 -4
  125. data/lib/rigor/sig_gen/classification.rb +12 -1
  126. data/lib/rigor/sig_gen/generator.rb +254 -5
  127. data/lib/rigor/sig_gen/renderer.rb +1 -6
  128. data/lib/rigor/sig_gen/writer.rb +3 -0
  129. data/lib/rigor/source/constant_path.rb +79 -2
  130. data/lib/rigor/triage/catalogue.rb +1 -1
  131. data/lib/rigor/type/combinator.rb +10 -0
  132. data/lib/rigor/type/maybe.rb +47 -0
  133. data/lib/rigor/type/refined.rb +1 -2
  134. data/lib/rigor/type/result.rb +53 -0
  135. data/lib/rigor/type.rb +2 -0
  136. data/lib/rigor/version.rb +1 -1
  137. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
  138. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
  139. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
  140. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
  141. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
  142. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
  143. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
  144. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
  145. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
  146. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
  147. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
  148. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
  149. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
  150. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
  151. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
  152. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
  153. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
  154. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
  155. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
  156. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
  157. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
  158. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
  159. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
  160. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
  161. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
  162. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
  163. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
  164. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
  165. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
  166. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
  167. data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
  168. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
  169. data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
  170. data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
  171. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
  172. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
  173. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
  174. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
  175. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
  176. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
  177. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
  178. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
  179. data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
  180. data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
  181. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
  182. data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
  183. data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
  184. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
  185. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
  186. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  187. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
  188. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
  189. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
  190. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
  191. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
  192. data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
  193. data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
  194. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
  195. data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
  196. data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
  197. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
  198. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
  199. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
  200. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
  201. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
  202. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
  203. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
  204. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
  205. data/sig/rigor/analysis/baseline.rbs +1 -1
  206. data/sig/rigor/environment.rbs +3 -0
  207. data/sig/rigor/inference.rbs +4 -1
  208. data/sig/rigor/plugin/io_boundary.rbs +2 -0
  209. data/sig/rigor/reflection.rbs +3 -1
  210. data/sig/rigor/scope.rbs +25 -1
  211. data/sig/rigor/type.rbs +95 -1
  212. data/sig/rigor.rbs +16 -0
  213. metadata +69 -3
@@ -25,8 +25,19 @@ module Rigor
25
25
  # Element and value types are translated recursively under the caller's `self_type` /
26
26
  # `instance_type` / `type_vars` context.
27
27
  #
28
- # Interface and intersection types still degrade to `Dynamic[Top]`; they are bound to acceptance
29
- # and dispatch rules not yet implemented.
28
+ # Alias and intersection types (#529):
29
+ # - `RBS::Types::Alias` resolves through the caller-supplied `alias_expander:` (an object answering
30
+ # `expand_type_alias`, in practice the environment's `RbsLoader`) and the expansion is translated
31
+ # in the same context. Without an expander — or past the expansion budget that bounds recursive
32
+ # aliases like `type json = ... | Array[json]` — the alias degrades to `Dynamic[Top]` as before.
33
+ # - `RBS::Types::Intersection` translates to its first member that carries static evidence. Every
34
+ # value of `A & B` IS an `A`, so any single member is a sound superset read; taking the first
35
+ # informative one turns prism's `type node = Node & _Node` into `Nominal[Prism::Node]` instead of
36
+ # `untyped`. `Type::Intersection` stays out of this path: it is the same-base refinement composite
37
+ # carrier, and the dispatch tiers do not accept it as a receiver.
38
+ #
39
+ # Interface types still degrade to `Dynamic[Top]`; they are bound to acceptance and dispatch rules
40
+ # not yet implemented.
30
41
  #
31
42
  # The optional `self_type:` and `instance_type:` arguments are the Rigor counterparts of RBS's
32
43
  # `self` and `instance` tokens:
@@ -64,8 +75,8 @@ module Rigor
64
75
  RBS::Types::Record => :translate_record,
65
76
  RBS::Types::Proc => :translate_proc_nominal,
66
77
  RBS::Types::ClassSingleton => :translate_class_singleton,
67
- RBS::Types::Alias => :translate_untyped,
68
- RBS::Types::Intersection => :translate_untyped,
78
+ RBS::Types::Alias => :translate_alias,
79
+ RBS::Types::Intersection => :translate_intersection,
69
80
  RBS::Types::Variable => :translate_variable,
70
81
  RBS::Types::Interface => :translate_untyped
71
82
  }.freeze
@@ -74,6 +85,21 @@ module Rigor
74
85
  EMPTY_TYPE_VARS = {}.freeze
75
86
  private_constant :EMPTY_TYPE_VARS
76
87
 
88
+ # The immutable translation context threaded through every handler. `alias_depth` counts alias
89
+ # expansions performed on the current walk, so a recursive alias terminates instead of looping.
90
+ Context = Struct.new(:self_type, :instance_type, :type_vars, :alias_expander, :alias_depth) do
91
+ def deeper
92
+ self.class.new(self_type, instance_type, type_vars, alias_expander, alias_depth + 1)
93
+ end
94
+ end
95
+ private_constant :Context
96
+
97
+ # Eight expansions cover any observed nesting (gem sigs rarely go past aliases-of-aliases) while
98
+ # bounding the `type json = ... | Array[json]` family: the ninth nested expansion degrades to
99
+ # `Dynamic[Top]`, which is exactly what every alias read before #529.
100
+ ALIAS_EXPANSION_LIMIT = 8
101
+ private_constant :ALIAS_EXPANSION_LIMIT
102
+
77
103
  class << self
78
104
  # @param rbs_type [RBS::Types::Bases::Base, RBS::Types::ClassInstance, ...]
79
105
  # @param self_type [Rigor::Type, nil] substitute for `Bases::Self`.
@@ -82,36 +108,43 @@ module Rigor
82
108
  # @param type_vars [Hash{Symbol => Rigor::Type}] substitution map for `Bases::Variable`. Keys
83
109
  # are the RBS variable names (e.g., `:Elem`); values are Rigor types that replace the
84
110
  # variable. Variables that are not bound in the map degrade to Dynamic[Top].
111
+ # @param alias_expander [#expand_type_alias, nil] resolves `RBS::Types::Alias` one level out —
112
+ # in practice the environment's `RbsLoader`. When nil, aliases degrade to Dynamic[Top].
85
113
  # @return [Rigor::Type]
86
- def translate(rbs_type, self_type: nil, instance_type: nil, type_vars: EMPTY_TYPE_VARS)
114
+ def translate(rbs_type, self_type: nil, instance_type: nil, type_vars: EMPTY_TYPE_VARS,
115
+ alias_expander: nil)
116
+ translate_in(rbs_type, Context.new(self_type, instance_type, type_vars, alias_expander, 0))
117
+ end
118
+
119
+ private
120
+
121
+ def translate_in(rbs_type, context)
87
122
  handler = TRANSLATORS[rbs_type.class]
88
- return send(handler, rbs_type, self_type, instance_type, type_vars) if handler
123
+ return send(handler, rbs_type, context) if handler
89
124
 
90
125
  Type::Combinator.untyped
91
126
  end
92
127
 
93
- private
94
-
95
- def translate_top(_rbs_type, _self_type, _instance_type, _type_vars)
128
+ def translate_top(_rbs_type, _context)
96
129
  Type::Combinator.top
97
130
  end
98
131
 
99
- def translate_bot(_rbs_type, _self_type, _instance_type, _type_vars)
132
+ def translate_bot(_rbs_type, _context)
100
133
  Type::Combinator.bot
101
134
  end
102
135
 
103
- def translate_untyped(_rbs_type, _self_type, _instance_type, _type_vars)
136
+ def translate_untyped(_rbs_type, _context)
104
137
  Type::Combinator.untyped
105
138
  end
106
139
 
107
- def translate_nil(_rbs_type, _self_type, _instance_type, _type_vars)
140
+ def translate_nil(_rbs_type, _context)
108
141
  Type::Combinator.constant_of(nil)
109
142
  end
110
143
 
111
144
  # `bool` in RBS denotes `true | false`. We fold it to that union eagerly so downstream
112
145
  # comparisons (e.g., `result == Constant[true]`) remain structural. Memoized at the module
113
146
  # level because the union is value-equal across calls.
114
- def translate_bool(_rbs_type, _self_type, _instance_type, _type_vars)
147
+ def translate_bool(_rbs_type, _context)
115
148
  BOOL_UNION
116
149
  end
117
150
 
@@ -121,60 +154,54 @@ module Rigor
121
154
  ).freeze
122
155
  private_constant :BOOL_UNION
123
156
 
124
- def translate_self(_rbs_type, self_type, _instance_type, _type_vars)
125
- self_type || Type::Combinator.untyped
157
+ def translate_self(_rbs_type, context)
158
+ context.self_type || Type::Combinator.untyped
126
159
  end
127
160
 
128
- def translate_instance(_rbs_type, _self_type, instance_type, _type_vars)
129
- instance_type || Type::Combinator.untyped
161
+ def translate_instance(_rbs_type, context)
162
+ context.instance_type || Type::Combinator.untyped
130
163
  end
131
164
 
132
- def translate_optional(rbs_type, self_type, instance_type, type_vars)
133
- inner = translate(rbs_type.type, self_type: self_type, instance_type: instance_type, type_vars: type_vars)
165
+ def translate_optional(rbs_type, context)
166
+ inner = translate_in(rbs_type.type, context)
134
167
  Type::Combinator.union(inner, Type::Combinator.constant_of(nil))
135
168
  end
136
169
 
137
- def translate_union(rbs_type, self_type, instance_type, type_vars)
138
- members = rbs_type.types.map do |t|
139
- translate(t, self_type: self_type, instance_type: instance_type, type_vars: type_vars)
140
- end
170
+ def translate_union(rbs_type, context)
171
+ members = rbs_type.types.map { |t| translate_in(t, context) }
141
172
  Type::Combinator.union(*members)
142
173
  end
143
174
 
144
- def translate_literal(rbs_type, _self_type, _instance_type, _type_vars)
175
+ def translate_literal(rbs_type, _context)
145
176
  Type::Combinator.constant_of(rbs_type.literal)
146
177
  end
147
178
 
148
179
  # Translates the type arguments recursively so `Array[Integer]` round-trips into
149
180
  # `Nominal["Array", [Nominal["Integer"]]]`. Variables inside the args participate in
150
181
  # substitution through the same `type_vars:` map.
151
- def translate_class_instance(rbs_type, self_type, instance_type, type_vars)
182
+ def translate_class_instance(rbs_type, context)
152
183
  name = rbs_type.name.relative!.to_s
153
- translated_args = rbs_type.args.map do |arg|
154
- translate(arg, self_type: self_type, instance_type: instance_type, type_vars: type_vars)
155
- end
184
+ translated_args = rbs_type.args.map { |arg| translate_in(arg, context) }
156
185
  Type::Combinator.nominal_of(name, type_args: translated_args)
157
186
  end
158
187
 
159
188
  # Preserves tuple precision through the boundary. Each positional element type is translated
160
189
  # recursively under the caller's substitution context, and the resulting list is wrapped in a
161
190
  # `Rigor::Type::Tuple`.
162
- def translate_tuple(rbs_type, self_type, instance_type, type_vars)
163
- elements = rbs_type.types.map do |t|
164
- translate(t, self_type: self_type, instance_type: instance_type, type_vars: type_vars)
165
- end
191
+ def translate_tuple(rbs_type, context)
192
+ elements = rbs_type.types.map { |t| translate_in(t, context) }
166
193
  Type::Combinator.tuple_of(*elements)
167
194
  end
168
195
 
169
196
  # Preserves hash-record precision through the boundary. RBS records use Symbol keys; the
170
197
  # translator keeps them as Symbol keys on the resulting exact closed HashShape so erasure can
171
198
  # round-trip back to `{ a: T, ?b: U }` syntax.
172
- def translate_record(rbs_type, self_type, instance_type, type_vars)
199
+ def translate_record(rbs_type, context)
173
200
  pairs = rbs_type.fields.each_with_object({}) do |(key, value), acc|
174
- acc[key] = translate(value, self_type: self_type, instance_type: instance_type, type_vars: type_vars)
201
+ acc[key] = translate_in(value, context)
175
202
  end
176
203
  optional_pairs = rbs_type.optional_fields.each_with_object({}) do |(key, value), acc|
177
- acc[key] = translate(value, self_type: self_type, instance_type: instance_type, type_vars: type_vars)
204
+ acc[key] = translate_in(value, context)
178
205
  end
179
206
  Type::Combinator.hash_shape_of(
180
207
  pairs.merge(optional_pairs),
@@ -184,22 +211,45 @@ module Rigor
184
211
  )
185
212
  end
186
213
 
187
- def translate_proc_nominal(_rbs_type, _self_type, _instance_type, _type_vars)
214
+ def translate_proc_nominal(_rbs_type, _context)
188
215
  Type::Combinator.nominal_of(Proc)
189
216
  end
190
217
 
191
218
  # `singleton(Foo)` is the type of the constant `Foo` itself (the class object). With the
192
219
  # dedicated Singleton type, we map directly to `Singleton[Foo]`.
193
- def translate_class_singleton(rbs_type, _self_type, _instance_type, _type_vars)
220
+ def translate_class_singleton(rbs_type, _context)
194
221
  name = rbs_type.name.relative!.to_s
195
222
  Type::Combinator.singleton_of(name)
196
223
  end
197
224
 
225
+ # #529 — sees through a type alias instead of reading `untyped`. `expand_type_alias` resolves
226
+ # one level (with the alias's own type params substituted, so generic aliases work); nested
227
+ # aliases in the expansion recurse back through here with the depth budget decremented. Every
228
+ # decline arm returns exactly what the alias translated to before this handler existed.
229
+ def translate_alias(rbs_type, context)
230
+ expander = context.alias_expander
231
+ return Type::Combinator.untyped if expander.nil? || context.alias_depth >= ALIAS_EXPANSION_LIMIT
232
+
233
+ expanded = expander.expand_type_alias(rbs_type)
234
+ return Type::Combinator.untyped if expanded.nil?
235
+
236
+ translate_in(expanded, context.deeper)
237
+ end
238
+
239
+ # #529 — `A & B` reads as its first member that carries static evidence. Every value of the
240
+ # intersection IS a value of each member, so any single member is a sound superset read; the
241
+ # skip list drops members that add nothing (an interface's `Dynamic[Top]`, `top` itself) so
242
+ # `Node & _Node` lands on `Nominal[Prism::Node]`. No informative member — the pre-#529 read.
243
+ def translate_intersection(rbs_type, context)
244
+ members = rbs_type.types.map { |t| translate_in(t, context) }
245
+ members.find { |m| !m.is_a?(Type::Dynamic) && !m.is_a?(Type::Top) } || Type::Combinator.untyped
246
+ end
247
+
198
248
  # Looks up the variable's RBS name in the substitution map; bound variables are replaced
199
249
  # inline, free variables degrade to Dynamic[Top]. We use `fetch` with a default rather than
200
250
  # `[]` so a deliberate `nil` binding (a caller mistake) is never silently consumed.
201
- def translate_variable(rbs_type, _self_type, _instance_type, type_vars)
202
- type_vars.fetch(rbs_type.name) { Type::Combinator.untyped }
251
+ def translate_variable(rbs_type, context)
252
+ context.type_vars.fetch(rbs_type.name) { Type::Combinator.untyped }
203
253
  end
204
254
  end
205
255
  end