rigortype 0.0.1
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.
- checksums.yaml +7 -0
- data/LICENSE +373 -0
- data/README.md +152 -0
- data/exe/rigor +9 -0
- data/lib/rigor/analysis/check_rules.rb +503 -0
- data/lib/rigor/analysis/diagnostic.rb +35 -0
- data/lib/rigor/analysis/fact_store.rb +133 -0
- data/lib/rigor/analysis/result.rb +29 -0
- data/lib/rigor/analysis/runner.rb +119 -0
- data/lib/rigor/ast/type_node.rb +41 -0
- data/lib/rigor/ast.rb +22 -0
- data/lib/rigor/cli/type_of_command.rb +160 -0
- data/lib/rigor/cli/type_of_renderer.rb +88 -0
- data/lib/rigor/cli/type_scan_command.rb +160 -0
- data/lib/rigor/cli/type_scan_renderer.rb +165 -0
- data/lib/rigor/cli/type_scan_report.rb +32 -0
- data/lib/rigor/cli.rb +195 -0
- data/lib/rigor/configuration.rb +49 -0
- data/lib/rigor/environment/class_registry.rb +141 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +64 -0
- data/lib/rigor/environment/rbs_loader.rb +244 -0
- data/lib/rigor/environment.rb +177 -0
- data/lib/rigor/inference/acceptance.rb +444 -0
- data/lib/rigor/inference/block_parameter_binder.rb +198 -0
- data/lib/rigor/inference/closure_escape_analyzer.rb +191 -0
- data/lib/rigor/inference/coverage_scanner.rb +85 -0
- data/lib/rigor/inference/expression_typer.rb +831 -0
- data/lib/rigor/inference/fallback.rb +35 -0
- data/lib/rigor/inference/fallback_tracer.rb +64 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +102 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +169 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +421 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +336 -0
- data/lib/rigor/inference/method_dispatcher.rb +213 -0
- data/lib/rigor/inference/method_parameter_binder.rb +257 -0
- data/lib/rigor/inference/multi_target_binder.rb +143 -0
- data/lib/rigor/inference/narrowing.rb +1008 -0
- data/lib/rigor/inference/rbs_type_translator.rb +219 -0
- data/lib/rigor/inference/scope_indexer.rb +468 -0
- data/lib/rigor/inference/statement_evaluator.rb +1017 -0
- data/lib/rigor/rbs_extended.rb +98 -0
- data/lib/rigor/scope.rb +340 -0
- data/lib/rigor/source/node_locator.rb +104 -0
- data/lib/rigor/source/node_walker.rb +37 -0
- data/lib/rigor/source.rb +15 -0
- data/lib/rigor/testing.rb +65 -0
- data/lib/rigor/trinary.rb +108 -0
- data/lib/rigor/type/accepts_result.rb +109 -0
- data/lib/rigor/type/bot.rb +57 -0
- data/lib/rigor/type/combinator.rb +148 -0
- data/lib/rigor/type/constant.rb +90 -0
- data/lib/rigor/type/dynamic.rb +60 -0
- data/lib/rigor/type/hash_shape.rb +246 -0
- data/lib/rigor/type/nominal.rb +83 -0
- data/lib/rigor/type/singleton.rb +65 -0
- data/lib/rigor/type/top.rb +56 -0
- data/lib/rigor/type/tuple.rb +84 -0
- data/lib/rigor/type/union.rb +65 -0
- data/lib/rigor/type.rb +23 -0
- data/lib/rigor/version.rb +5 -0
- data/lib/rigor.rb +29 -0
- data/sig/rigor/analysis/fact_store.rbs +51 -0
- data/sig/rigor/ast.rbs +11 -0
- data/sig/rigor/environment.rbs +59 -0
- data/sig/rigor/inference.rbs +151 -0
- data/sig/rigor/rbs_extended.rbs +22 -0
- data/sig/rigor/scope.rbs +49 -0
- data/sig/rigor/source.rbs +20 -0
- data/sig/rigor/testing.rbs +9 -0
- data/sig/rigor/trinary.rbs +29 -0
- data/sig/rigor/type.rbs +171 -0
- data/sig/rigor.rbs +70 -0
- metadata +260 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../type"
|
|
6
|
+
require_relative "rbs_type_translator"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
# Builds the entry scope of a method body by translating the method's
|
|
11
|
+
# parameter list into a `name -> Rigor::Type` map.
|
|
12
|
+
#
|
|
13
|
+
# Parameter types come from the surrounding class's RBS signature
|
|
14
|
+
# when one is available; otherwise every parameter defaults to
|
|
15
|
+
# `Dynamic[Top]`. The default is the Slice 1 fail-soft answer for
|
|
16
|
+
# unknown values, so a method whose RBS signature is missing or
|
|
17
|
+
# whose parameters cannot be matched still binds every name into
|
|
18
|
+
# the scope (a method body whose `Local x` reads return
|
|
19
|
+
# `Dynamic[Top]` instead of falling through to the unbound-local
|
|
20
|
+
# `Dynamic[Top]` event is the same observable type, but the
|
|
21
|
+
# binding presence is what later slices need to attach narrowing
|
|
22
|
+
# facts to).
|
|
23
|
+
#
|
|
24
|
+
# The class context (`class_path:`) and `singleton:` flag are
|
|
25
|
+
# supplied by the caller (the StatementEvaluator) which threads
|
|
26
|
+
# them as the lexical class scope. The binder makes no assumption
|
|
27
|
+
# about how that context was computed; it only uses it to build
|
|
28
|
+
# the `(class_name, method_name)` lookup key for
|
|
29
|
+
# `Rigor::Environment::RbsLoader#instance_method` /
|
|
30
|
+
# `#singleton_method`.
|
|
31
|
+
#
|
|
32
|
+
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
33
|
+
# rubocop:disable Metrics/ClassLength
|
|
34
|
+
class MethodParameterBinder
|
|
35
|
+
# @param environment [Rigor::Environment]
|
|
36
|
+
# @param class_path [String, nil] the qualified name of the class
|
|
37
|
+
# the method is defined in (e.g., `"Foo::Bar"`), or `nil` for a
|
|
38
|
+
# top-level `def` outside any class. When `nil` (or when the
|
|
39
|
+
# class is unknown to RBS), every parameter falls back to
|
|
40
|
+
# `Dynamic[Top]`.
|
|
41
|
+
# @param singleton [Boolean] `true` when the def is a singleton
|
|
42
|
+
# method (either `def self.foo` or a `def foo` inside
|
|
43
|
+
# `class << self`); routes the lookup through
|
|
44
|
+
# `RbsLoader#singleton_method`.
|
|
45
|
+
def initialize(environment:, class_path:, singleton:)
|
|
46
|
+
@environment = environment
|
|
47
|
+
@class_path = class_path
|
|
48
|
+
@singleton = singleton
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param def_node [Prism::DefNode]
|
|
52
|
+
# @return [Hash{Symbol => Rigor::Type}] ordered map from parameter
|
|
53
|
+
# name to bound type. Anonymous parameters (`*` and `**` without
|
|
54
|
+
# a name) are skipped.
|
|
55
|
+
def bind(def_node)
|
|
56
|
+
slots = collect_slots(def_node.parameters)
|
|
57
|
+
types = default_types_for(slots)
|
|
58
|
+
|
|
59
|
+
rbs_method = lookup_rbs_method(def_node)
|
|
60
|
+
return types unless rbs_method
|
|
61
|
+
|
|
62
|
+
method_types = rbs_method.method_types
|
|
63
|
+
return types if method_types.empty?
|
|
64
|
+
|
|
65
|
+
apply_rbs_overloads(types, slots, method_types)
|
|
66
|
+
types
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
ParamSlot = Data.define(:kind, :name, :index)
|
|
72
|
+
private_constant :ParamSlot
|
|
73
|
+
|
|
74
|
+
# Walk the Prism `ParametersNode` and emit one slot per named
|
|
75
|
+
# parameter, in declaration order. Anonymous slots (rest /
|
|
76
|
+
# keyword-rest with no name) are skipped because we have no
|
|
77
|
+
# local name to bind. The slot's `:index` is the positional
|
|
78
|
+
# index for required/optional/trailing positionals (used to look
|
|
79
|
+
# up the matching RBS function param) and is `nil` for the
|
|
80
|
+
# singleton kinds (`:rest_positional`, `:rest_keyword`,
|
|
81
|
+
# `:block`).
|
|
82
|
+
def collect_slots(params_node)
|
|
83
|
+
return [] if params_node.nil?
|
|
84
|
+
|
|
85
|
+
slots = []
|
|
86
|
+
slots.concat(positional_slots(params_node))
|
|
87
|
+
slots.concat(keyword_slots(params_node))
|
|
88
|
+
append_rest_keyword_slot(slots, params_node)
|
|
89
|
+
append_block_slot(slots, params_node)
|
|
90
|
+
slots
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def positional_slots(params_node)
|
|
94
|
+
slots = []
|
|
95
|
+
params_node.requireds.each_with_index { |p, i| slots << ParamSlot.new(:required_positional, p.name, i) }
|
|
96
|
+
params_node.optionals.each_with_index { |p, i| slots << ParamSlot.new(:optional_positional, p.name, i) }
|
|
97
|
+
rest = params_node.rest
|
|
98
|
+
slots << ParamSlot.new(:rest_positional, rest.name, nil) if rest.respond_to?(:name) && rest&.name
|
|
99
|
+
params_node.posts.each_with_index { |p, i| slots << ParamSlot.new(:trailing_positional, p.name, i) }
|
|
100
|
+
slots
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def keyword_slots(params_node)
|
|
104
|
+
params_node.keywords.filter_map do |kw|
|
|
105
|
+
case kw
|
|
106
|
+
when Prism::RequiredKeywordParameterNode
|
|
107
|
+
ParamSlot.new(:required_keyword, kw.name, kw.name)
|
|
108
|
+
when Prism::OptionalKeywordParameterNode
|
|
109
|
+
ParamSlot.new(:optional_keyword, kw.name, kw.name)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def append_rest_keyword_slot(slots, params_node)
|
|
115
|
+
kw_rest = params_node.keyword_rest
|
|
116
|
+
return unless kw_rest.respond_to?(:name) && kw_rest&.name
|
|
117
|
+
|
|
118
|
+
slots << ParamSlot.new(:rest_keyword, kw_rest.name, nil)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def append_block_slot(slots, params_node)
|
|
122
|
+
block = params_node.block
|
|
123
|
+
return unless block.respond_to?(:name) && block&.name
|
|
124
|
+
|
|
125
|
+
slots << ParamSlot.new(:block, block.name, nil)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def default_types_for(slots)
|
|
129
|
+
slots.to_h { |slot| [slot.name, Type::Combinator.untyped] }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def lookup_rbs_method(def_node)
|
|
133
|
+
return nil if @class_path.nil?
|
|
134
|
+
|
|
135
|
+
loader = @environment.rbs_loader
|
|
136
|
+
return nil if loader.nil?
|
|
137
|
+
|
|
138
|
+
method_name = def_node.name
|
|
139
|
+
# `def self.foo` always means a singleton method on the
|
|
140
|
+
# immediate enclosing class. `def foo` inside `class << self`
|
|
141
|
+
# is also a singleton method (the StatementEvaluator threads
|
|
142
|
+
# the `singleton:` flag through this case).
|
|
143
|
+
if def_node.receiver.is_a?(Prism::SelfNode) || @singleton
|
|
144
|
+
loader.singleton_method(class_name: @class_path, method_name: method_name)
|
|
145
|
+
else
|
|
146
|
+
loader.instance_method(class_name: @class_path, method_name: method_name)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Bind each parameter slot to the union of the matching parameter
|
|
151
|
+
# types across every overload that *has* that slot. Overloads
|
|
152
|
+
# that omit the slot (e.g., `Array#first` has both `()` and
|
|
153
|
+
# `(int)` overloads — only the second matches a `def first(n)`
|
|
154
|
+
# redefinition) are silently skipped, so the binder defaults to
|
|
155
|
+
# the most informative type the RBS signature provides without
|
|
156
|
+
# having to know which overload the runtime will pick.
|
|
157
|
+
def apply_rbs_overloads(types, slots, method_types)
|
|
158
|
+
slots.each do |slot|
|
|
159
|
+
next if slot.name.nil?
|
|
160
|
+
|
|
161
|
+
translated = collect_translated_types(method_types, slot)
|
|
162
|
+
next if translated.empty?
|
|
163
|
+
|
|
164
|
+
types[slot.name] = build_slot_type(translated, slot.kind)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def collect_translated_types(method_types, slot)
|
|
169
|
+
rbs_types = method_types.flat_map do |mt|
|
|
170
|
+
t = rbs_type_for_slot(mt.type, slot)
|
|
171
|
+
t ? [t] : []
|
|
172
|
+
end
|
|
173
|
+
rbs_types.map { |t| translate_with_self(t) }.uniq
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def build_slot_type(translated, kind)
|
|
177
|
+
bound = translated.size == 1 ? translated.first : Type::Combinator.union(*translated)
|
|
178
|
+
wrap_for_kind(bound, kind)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Dispatch table from slot kind to a small lambda that pulls the
|
|
182
|
+
# matching RBS parameter type out of an `RBS::Types::Function`.
|
|
183
|
+
# The hash keeps `rbs_type_for_slot` linear (one lookup, one
|
|
184
|
+
# call) so the cyclomatic-complexity budget does not balloon as
|
|
185
|
+
# future slices add more parameter kinds (e.g., `**Symbol kw` is
|
|
186
|
+
# a candidate for a stricter route in Slice 5+).
|
|
187
|
+
# Match keyword parameters by name across both required and
|
|
188
|
+
# optional keyword maps. RBS may declare a keyword as optional
|
|
189
|
+
# (`?by:`) while the Ruby `def` lists it as required (or vice
|
|
190
|
+
# versa); the binding is by-name regardless of which side
|
|
191
|
+
# defines it.
|
|
192
|
+
KEYWORD_PROVIDER = lambda do |fn, slot|
|
|
193
|
+
fn.required_keywords[slot.name]&.type || fn.optional_keywords[slot.name]&.type
|
|
194
|
+
end
|
|
195
|
+
private_constant :KEYWORD_PROVIDER
|
|
196
|
+
|
|
197
|
+
RBS_TYPE_PROVIDERS = {
|
|
198
|
+
required_positional: ->(fn, slot) { fn.required_positionals[slot.index]&.type },
|
|
199
|
+
optional_positional: ->(fn, slot) { fn.optional_positionals[slot.index]&.type },
|
|
200
|
+
rest_positional: ->(fn, _slot) { fn.rest_positionals&.type },
|
|
201
|
+
trailing_positional: ->(fn, slot) { fn.trailing_positionals[slot.index]&.type },
|
|
202
|
+
required_keyword: KEYWORD_PROVIDER,
|
|
203
|
+
optional_keyword: KEYWORD_PROVIDER,
|
|
204
|
+
rest_keyword: ->(fn, _slot) { fn.rest_keywords&.type }
|
|
205
|
+
}.freeze
|
|
206
|
+
private_constant :RBS_TYPE_PROVIDERS
|
|
207
|
+
|
|
208
|
+
def rbs_type_for_slot(function, slot)
|
|
209
|
+
provider = RBS_TYPE_PROVIDERS[slot.kind]
|
|
210
|
+
return nil unless provider
|
|
211
|
+
|
|
212
|
+
provider.call(function, slot)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The variable bound to a `*rest` parameter is the *Array* of
|
|
216
|
+
# rest-positional arguments, not a single element. Likewise
|
|
217
|
+
# `**kw` is bound to a `Hash[Symbol, V]`. Wrap the translated
|
|
218
|
+
# element/value type accordingly so `rest` reads as
|
|
219
|
+
# `Array[Integer]` rather than `Integer`.
|
|
220
|
+
def wrap_for_kind(translated, kind)
|
|
221
|
+
case kind
|
|
222
|
+
when :rest_positional
|
|
223
|
+
Type::Combinator.nominal_of("Array", type_args: [translated])
|
|
224
|
+
when :rest_keyword
|
|
225
|
+
symbol_nominal = Type::Combinator.nominal_of("Symbol")
|
|
226
|
+
Type::Combinator.nominal_of("Hash", type_args: [symbol_nominal, translated])
|
|
227
|
+
else
|
|
228
|
+
translated
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def translate_with_self(rbs_type)
|
|
233
|
+
self_type, instance_type = self_and_instance_type
|
|
234
|
+
RbsTypeTranslator.translate(
|
|
235
|
+
rbs_type,
|
|
236
|
+
self_type: self_type,
|
|
237
|
+
instance_type: instance_type
|
|
238
|
+
)
|
|
239
|
+
rescue StandardError
|
|
240
|
+
Type::Combinator.untyped
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def self_and_instance_type
|
|
244
|
+
return [nil, nil] if @class_path.nil?
|
|
245
|
+
|
|
246
|
+
instance = @environment.nominal_for_name(@class_path)
|
|
247
|
+
if @singleton
|
|
248
|
+
singleton = @environment.singleton_for_name(@class_path)
|
|
249
|
+
[singleton, instance]
|
|
250
|
+
else
|
|
251
|
+
[instance, instance]
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
# rubocop:enable Metrics/ClassLength
|
|
256
|
+
end
|
|
257
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../type"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
# Slice 5 phase 2 sub-phase 2 destructuring binder.
|
|
10
|
+
#
|
|
11
|
+
# `Rigor::Inference::MultiTargetBinder` decomposes a tuple-shaped
|
|
12
|
+
# right-hand side type against a Prism multi-target tree and
|
|
13
|
+
# produces a `name -> Rigor::Type` binding map. The binder is
|
|
14
|
+
# shared between two surfaces:
|
|
15
|
+
#
|
|
16
|
+
# 1. `Rigor::Inference::StatementEvaluator#eval_multi_write` for
|
|
17
|
+
# the statement-level `a, b = rhs` form (`Prism::MultiWriteNode`).
|
|
18
|
+
# 2. `Rigor::Inference::BlockParameterBinder` for nested
|
|
19
|
+
# destructuring inside block parameter lists
|
|
20
|
+
# (`Prism::MultiTargetNode` under `BlockParametersNode#requireds`).
|
|
21
|
+
#
|
|
22
|
+
# Both Prism nodes share the same `lefts` / `rest` (a
|
|
23
|
+
# `Prism::SplatNode`) / `rights` triple, so the binder treats them
|
|
24
|
+
# uniformly. The binder is pure: it MUST NOT mutate its inputs and
|
|
25
|
+
# MUST return a fresh `Hash` on every call.
|
|
26
|
+
#
|
|
27
|
+
# The binder threads `Type::Tuple` decompositions when the
|
|
28
|
+
# right-hand side carrier is a known-arity tuple. Other carriers
|
|
29
|
+
# (`Nominal[Array]`, `Dynamic[Top]`, `Top`, `Bot`, ...) collapse
|
|
30
|
+
# to `Dynamic[Top]` per slot — Slice 5 phase 2 sub-phase 2 stays
|
|
31
|
+
# conservative on dynamic-arity right-hand sides until the
|
|
32
|
+
# narrower receiver-shape lattice lands.
|
|
33
|
+
#
|
|
34
|
+
# Targets the binder recognises:
|
|
35
|
+
#
|
|
36
|
+
# - `Prism::LocalVariableTargetNode` — used by the statement-level
|
|
37
|
+
# `a, b = rhs` form. Binds `target.name` to its slice of the
|
|
38
|
+
# right-hand side.
|
|
39
|
+
# - `Prism::RequiredParameterNode` — used by block-parameter
|
|
40
|
+
# destructuring (`|(a, b), c|`). Prism encodes the inner names
|
|
41
|
+
# of a block-side `MultiTargetNode` as parameter nodes rather
|
|
42
|
+
# than target nodes; the binder treats them uniformly with
|
|
43
|
+
# their `LocalVariableTargetNode` cousins because they carry
|
|
44
|
+
# the same `name:` field and the same observable semantics
|
|
45
|
+
# (binding a fresh local in the block-entry scope).
|
|
46
|
+
# - `Prism::MultiTargetNode` — recurses with the slot's type as
|
|
47
|
+
# the new right-hand side.
|
|
48
|
+
# - `Prism::SplatNode` (used for `rest`) — its `expression` MUST
|
|
49
|
+
# be a `Prism::LocalVariableTargetNode` or a
|
|
50
|
+
# `Prism::RequiredParameterNode` to be observable; an anonymous
|
|
51
|
+
# `*` splat or a non-local target is skipped.
|
|
52
|
+
#
|
|
53
|
+
# Other target kinds (`InstanceVariableTargetNode`,
|
|
54
|
+
# `ConstantTargetNode`, `IndexTargetNode`, `CallTargetNode`,
|
|
55
|
+
# `ConstantPathTargetNode`, `ImplicitRestNode`, ...) MUST be
|
|
56
|
+
# silently skipped: they have no observable contribution to the
|
|
57
|
+
# local-variable scope the StatementEvaluator threads.
|
|
58
|
+
#
|
|
59
|
+
# See docs/internal-spec/inference-engine.md for the binding
|
|
60
|
+
# contract and docs/adr/4-type-inference-engine.md for the slice
|
|
61
|
+
# rationale.
|
|
62
|
+
module MultiTargetBinder
|
|
63
|
+
module_function
|
|
64
|
+
|
|
65
|
+
# @param target_node [Prism::MultiWriteNode, Prism::MultiTargetNode]
|
|
66
|
+
# @param rhs_type [Rigor::Type] type of the right-hand side
|
|
67
|
+
# @return [Hash{Symbol => Rigor::Type}]
|
|
68
|
+
def bind(target_node, rhs_type)
|
|
69
|
+
bindings = {}
|
|
70
|
+
visit(target_node, rhs_type, bindings)
|
|
71
|
+
bindings
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class << self
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def visit(node, rhs_type, bindings)
|
|
78
|
+
lefts = node.lefts || []
|
|
79
|
+
rest = node.rest
|
|
80
|
+
rights = node.rights || []
|
|
81
|
+
|
|
82
|
+
fronts, rest_type, backs = decompose(rhs_type, lefts.size, rights.size, rest_present: !rest.nil?)
|
|
83
|
+
lefts.each_with_index { |t, i| bind_target(t, fronts[i], bindings) }
|
|
84
|
+
bind_rest_target(rest, rest_type, bindings) if rest
|
|
85
|
+
rights.each_with_index { |t, i| bind_target(t, backs[i], bindings) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Decomposes the right-hand side type into the per-slot
|
|
89
|
+
# types. Returns a `[fronts, rest_type, backs]` triple, with
|
|
90
|
+
# `fronts` and `backs` each an ordered array of length
|
|
91
|
+
# `front_count`/`back_count`, and `rest_type` either a
|
|
92
|
+
# `Rigor::Type` (when `rest_present:` is true) or `nil`.
|
|
93
|
+
def decompose(rhs_type, front_count, back_count, rest_present:)
|
|
94
|
+
if rhs_type.is_a?(Type::Tuple)
|
|
95
|
+
decompose_tuple(rhs_type, front_count, back_count, rest_present: rest_present)
|
|
96
|
+
else
|
|
97
|
+
decompose_default(front_count, back_count, rest_present: rest_present)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def decompose_tuple(tuple, front_count, back_count, rest_present:)
|
|
102
|
+
elements = tuple.elements
|
|
103
|
+
fronts = Array.new(front_count) { |i| elements[i] || Type::Combinator.constant_of(nil) }
|
|
104
|
+
if rest_present
|
|
105
|
+
middle_end = [elements.size - back_count, front_count].max
|
|
106
|
+
middle = elements[front_count...middle_end] || []
|
|
107
|
+
rest_type = Type::Combinator.tuple_of(*middle)
|
|
108
|
+
backs = Array.new(back_count) { |i| elements[middle_end + i] || Type::Combinator.constant_of(nil) }
|
|
109
|
+
else
|
|
110
|
+
rest_type = nil
|
|
111
|
+
backs = Array.new(back_count) { |i| elements[front_count + i] || Type::Combinator.constant_of(nil) }
|
|
112
|
+
end
|
|
113
|
+
[fronts, rest_type, backs]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def decompose_default(front_count, back_count, rest_present:)
|
|
117
|
+
[
|
|
118
|
+
Array.new(front_count) { Type::Combinator.untyped },
|
|
119
|
+
rest_present ? Type::Combinator.untyped : nil,
|
|
120
|
+
Array.new(back_count) { Type::Combinator.untyped }
|
|
121
|
+
]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def bind_target(target, type, bindings)
|
|
125
|
+
case target
|
|
126
|
+
when Prism::LocalVariableTargetNode, Prism::RequiredParameterNode
|
|
127
|
+
bindings[target.name] = type
|
|
128
|
+
when Prism::MultiTargetNode
|
|
129
|
+
visit(target, type, bindings)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def bind_rest_target(splat_node, type, bindings)
|
|
134
|
+
expression = splat_node.expression
|
|
135
|
+
case expression
|
|
136
|
+
when Prism::LocalVariableTargetNode, Prism::RequiredParameterNode
|
|
137
|
+
bindings[expression.name] = type
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|