rigortype 0.0.2 → 0.0.4
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 +4 -4
- data/README.md +24 -7
- data/data/builtins/ruby_core/array.yml +1470 -0
- data/data/builtins/ruby_core/file.yml +501 -0
- data/data/builtins/ruby_core/hash.yml +936 -0
- data/data/builtins/ruby_core/io.yml +1594 -0
- data/data/builtins/ruby_core/numeric.yml +1809 -0
- data/data/builtins/ruby_core/range.yml +389 -0
- data/data/builtins/ruby_core/set.yml +594 -0
- data/data/builtins/ruby_core/string.yml +1850 -0
- data/data/builtins/ruby_core/time.yml +750 -0
- data/lib/rigor/analysis/check_rules.rb +97 -4
- data/lib/rigor/analysis/runner.rb +4 -0
- data/lib/rigor/builtins/imported_refinements.rb +251 -0
- data/lib/rigor/configuration.rb +6 -1
- data/lib/rigor/inference/acceptance.rb +324 -6
- data/lib/rigor/inference/builtins/array_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/hash_catalog.rb +40 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +90 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +93 -0
- data/lib/rigor/inference/builtins/range_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/set_catalog.rb +54 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +39 -0
- data/lib/rigor/inference/builtins/time_catalog.rb +64 -0
- data/lib/rigor/inference/expression_typer.rb +48 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +670 -16
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +144 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +215 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -7
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +240 -4
- data/lib/rigor/inference/method_dispatcher.rb +28 -21
- data/lib/rigor/inference/method_parameter_binder.rb +29 -4
- data/lib/rigor/inference/narrowing.rb +376 -4
- data/lib/rigor/inference/scope_indexer.rb +10 -2
- data/lib/rigor/inference/statement_evaluator.rb +213 -2
- data/lib/rigor/rbs_extended.rb +230 -15
- data/lib/rigor/scope.rb +14 -0
- data/lib/rigor/type/combinator.rb +159 -1
- data/lib/rigor/type/difference.rb +155 -0
- data/lib/rigor/type/integer_range.rb +137 -0
- data/lib/rigor/type/intersection.rb +135 -0
- data/lib/rigor/type/refined.rb +174 -0
- data/lib/rigor/type.rb +4 -0
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/rbs_extended.rbs +14 -0
- data/sig/rigor/scope.rbs +1 -0
- data/sig/rigor/type.rbs +91 -1
- metadata +25 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# IO / File support — the pure-path-manipulation tier.
|
|
9
|
+
#
|
|
10
|
+
# File and IO carry a lot of side-effecting surface (filesystem
|
|
11
|
+
# reads, descriptor mutations, line iteration) the analyzer
|
|
12
|
+
# cannot fold. Several `File` class methods, however, are
|
|
13
|
+
# functions over their path-string arguments — they do NOT
|
|
14
|
+
# touch the filesystem and do NOT depend on the current
|
|
15
|
+
# working directory.
|
|
16
|
+
#
|
|
17
|
+
# Folding them is platform-sensitive: every recognised method
|
|
18
|
+
# ([:basename, :dirname, :extname, :join, :split,
|
|
19
|
+
# :absolute_path?]) reads `File::SEPARATOR` /
|
|
20
|
+
# `File::ALT_SEPARATOR` and produces different answers on
|
|
21
|
+
# Windows vs POSIX hosts. The Ruby process running the
|
|
22
|
+
# analyzer hosts ONE platform; folding to a `Constant<String>`
|
|
23
|
+
# would silently bake that platform's answer into the
|
|
24
|
+
# analyzer's result and mis-report it on a host with a
|
|
25
|
+
# different separator policy.
|
|
26
|
+
#
|
|
27
|
+
# Default policy (`fold_platform_specific_paths == false`):
|
|
28
|
+
# decline the fold so the RBS tier answers with `Nominal[String]`
|
|
29
|
+
# / `Tuple[Nominal[String], Nominal[String]]` / `bool`. That is
|
|
30
|
+
# the platform-agnostic envelope — every concrete answer the
|
|
31
|
+
# method could legally return on any platform fits inside it.
|
|
32
|
+
# The future `non-empty-string` refinement carrier (see
|
|
33
|
+
# [imported-built-in-types.md](../../../../../docs/type-specification/imported-built-in-types.md))
|
|
34
|
+
# will tighten the basename/dirname/join cases further without
|
|
35
|
+
# leaking platform specifics; today we leave them at the
|
|
36
|
+
# nominal envelope.
|
|
37
|
+
#
|
|
38
|
+
# Opt-in policy (`fold_platform_specific_paths == true`):
|
|
39
|
+
# the analyzer trusts that its host platform matches the
|
|
40
|
+
# callers' deployment target and folds to a precise
|
|
41
|
+
# `Constant<String>`. Single-platform projects (most internal
|
|
42
|
+
# tooling, Rails apps deployed to Linux containers) can
|
|
43
|
+
# enable this in `.rigor.yml`:
|
|
44
|
+
#
|
|
45
|
+
# fold_platform_specific_paths: true
|
|
46
|
+
#
|
|
47
|
+
# The runner reads this on startup (`Rigor::Analysis::Runner`)
|
|
48
|
+
# and writes the flag here. Tests toggle the flag explicitly.
|
|
49
|
+
#
|
|
50
|
+
# See [ADR-5 — robustness principle](../../../../../docs/adr/5-robustness-principle.md):
|
|
51
|
+
# the platform-agnostic default is clause-1 of the principle
|
|
52
|
+
# applied with the constraint that "as strict as can be
|
|
53
|
+
# *correctness-preservingly* proved" excludes Constants whose
|
|
54
|
+
# value is host-specific.
|
|
55
|
+
module FileFolding
|
|
56
|
+
# File class methods that the analyzer can fold *when the
|
|
57
|
+
# fold is platform-safe to perform*. Today every entry is
|
|
58
|
+
# platform-sensitive (every one observes `File::SEPARATOR`
|
|
59
|
+
# or `File::ALT_SEPARATOR`); the gate below requires the
|
|
60
|
+
# opt-in flag for any of them to fire.
|
|
61
|
+
FILE_PURE_CLASS_METHODS = Set[
|
|
62
|
+
:basename,
|
|
63
|
+
:dirname,
|
|
64
|
+
:extname,
|
|
65
|
+
:join,
|
|
66
|
+
:split,
|
|
67
|
+
:absolute_path?
|
|
68
|
+
].freeze
|
|
69
|
+
private_constant :FILE_PURE_CLASS_METHODS
|
|
70
|
+
|
|
71
|
+
# Methods whose result depends on host directory-separator
|
|
72
|
+
# semantics (`/` on POSIX, `/` AND `\` on Windows, drive
|
|
73
|
+
# letters, UNC paths). Folding these would bake the
|
|
74
|
+
# analyzer-host's platform into the inferred type. The opt-
|
|
75
|
+
# in flag below controls whether to do it anyway.
|
|
76
|
+
PLATFORM_DEPENDENT_METHODS = Set[
|
|
77
|
+
:basename, :dirname, :extname, :join, :split, :absolute_path?
|
|
78
|
+
].freeze
|
|
79
|
+
private_constant :PLATFORM_DEPENDENT_METHODS
|
|
80
|
+
|
|
81
|
+
class << self
|
|
82
|
+
# Module-global flag. The runner sets it from
|
|
83
|
+
# `Rigor::Configuration#fold_platform_specific_paths`.
|
|
84
|
+
# Tests toggle it directly.
|
|
85
|
+
attr_accessor :fold_platform_specific_paths
|
|
86
|
+
end
|
|
87
|
+
self.fold_platform_specific_paths = false
|
|
88
|
+
|
|
89
|
+
module_function
|
|
90
|
+
|
|
91
|
+
# @return [Rigor::Type, nil] folded result, or nil to defer
|
|
92
|
+
# to the next dispatcher tier.
|
|
93
|
+
def try_dispatch(receiver:, method_name:, args:)
|
|
94
|
+
return nil unless dispatch_target?(receiver)
|
|
95
|
+
return nil unless FILE_PURE_CLASS_METHODS.include?(method_name)
|
|
96
|
+
return nil if platform_specific_skip?(method_name)
|
|
97
|
+
|
|
98
|
+
string_args = constant_string_args(args)
|
|
99
|
+
return nil if string_args.nil?
|
|
100
|
+
|
|
101
|
+
fold_class_method(method_name, string_args)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def platform_specific_skip?(method_name)
|
|
105
|
+
PLATFORM_DEPENDENT_METHODS.include?(method_name) &&
|
|
106
|
+
!FileFolding.fold_platform_specific_paths
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def dispatch_target?(receiver)
|
|
110
|
+
receiver.is_a?(Type::Singleton) && receiver.class_name == "File"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def constant_string_args(args)
|
|
114
|
+
return [] if args.empty?
|
|
115
|
+
return nil unless args.all? { |arg| constant_string_arg?(arg) }
|
|
116
|
+
|
|
117
|
+
args.map { |arg| arg.value.to_s }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def constant_string_arg?(arg)
|
|
121
|
+
arg.is_a?(Type::Constant) && (arg.value.is_a?(String) || arg.value.is_a?(Symbol))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def fold_class_method(method_name, string_args)
|
|
125
|
+
result = File.public_send(method_name, *string_args)
|
|
126
|
+
wrap_result(result)
|
|
127
|
+
rescue StandardError
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def wrap_result(result)
|
|
132
|
+
case result
|
|
133
|
+
when String, true, false
|
|
134
|
+
Type::Combinator.constant_of(result)
|
|
135
|
+
when Array
|
|
136
|
+
return nil unless result.all?(String)
|
|
137
|
+
|
|
138
|
+
Type::Combinator.tuple_of(*result.map { |s| Type::Combinator.constant_of(s) })
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# Iterator-style block-parameter typing.
|
|
9
|
+
#
|
|
10
|
+
# Sits ahead of `RbsDispatch.block_param_types` so the precise
|
|
11
|
+
# integer bounds for `Integer#times` / `Integer#upto` /
|
|
12
|
+
# `Integer#downto` reach the block body's parameter binder.
|
|
13
|
+
# Without this tier the RBS signature for `Integer#times`
|
|
14
|
+
# widens the index to `Nominal[Integer]`, dropping every
|
|
15
|
+
# bound the receiver carries.
|
|
16
|
+
#
|
|
17
|
+
# Each rule mirrors Ruby's actual iteration semantics:
|
|
18
|
+
#
|
|
19
|
+
# - `n.times { |i| … }` yields `i ∈ [0, n-1]` when `n > 0`,
|
|
20
|
+
# nothing otherwise. The block-param type is therefore
|
|
21
|
+
# `int<0, n-1>` for a `Constant<Integer>` receiver,
|
|
22
|
+
# `int<0, upper-1>` for a finite `IntegerRange`, and
|
|
23
|
+
# `non_negative_int` for any unbounded-above shape.
|
|
24
|
+
# - `a.upto(b) { |i| … }` yields `i ∈ [a, b]` when `a <= b`.
|
|
25
|
+
# Lower bound from the receiver, upper bound from the
|
|
26
|
+
# argument.
|
|
27
|
+
# - `a.downto(b) { |i| … }` yields the same domain `[b, a]`,
|
|
28
|
+
# just iterated in reverse. Lower bound from the
|
|
29
|
+
# argument, upper bound from the receiver.
|
|
30
|
+
module IteratorDispatch # rubocop:disable Metrics/ModuleLength
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# @return [Array<Rigor::Type>, nil] block-param types, or
|
|
34
|
+
# nil to fall through to the next tier.
|
|
35
|
+
def block_param_types(receiver:, method_name:, args:)
|
|
36
|
+
case method_name
|
|
37
|
+
when :times then times_block_params(receiver)
|
|
38
|
+
when :upto then upto_block_params(receiver, args.first)
|
|
39
|
+
when :downto then downto_block_params(receiver, args.first)
|
|
40
|
+
when :each_with_index then each_with_index_block_params(receiver)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def times_block_params(receiver)
|
|
45
|
+
return nil unless integer_rooted?(receiver)
|
|
46
|
+
|
|
47
|
+
upper = upper_bound_of(receiver)
|
|
48
|
+
return [Type::Combinator.non_negative_int] unless upper.is_a?(Integer)
|
|
49
|
+
return [Type::Combinator.non_negative_int] unless upper.positive?
|
|
50
|
+
|
|
51
|
+
[build_index_range(0, upper - 1)]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def upto_block_params(receiver, end_arg)
|
|
55
|
+
return nil unless integer_rooted?(receiver) && integer_rooted?(end_arg)
|
|
56
|
+
|
|
57
|
+
[build_index_range(lower_bound_of(receiver), upper_bound_of(end_arg))]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def downto_block_params(receiver, end_arg)
|
|
61
|
+
return nil unless integer_rooted?(receiver) && integer_rooted?(end_arg)
|
|
62
|
+
|
|
63
|
+
[build_index_range(lower_bound_of(end_arg), upper_bound_of(receiver))]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Generalised iterator: every Enumerable-shaped collection
|
|
67
|
+
# in v0.0.4 yields `(element, index)` where the index is
|
|
68
|
+
# always `non-negative-int`. The element comes from the
|
|
69
|
+
# receiver's shape:
|
|
70
|
+
#
|
|
71
|
+
# - `Array[T]` / `Set[T]` / `Range[T]` → T
|
|
72
|
+
# - `Tuple[A, B, C]` → A | B | C
|
|
73
|
+
# (empty tuple cannot iterate, but we conservatively
|
|
74
|
+
# fall through to RBS so a missing rule never throws)
|
|
75
|
+
# - `Hash[K, V]` / `HashShape{...}` → Tuple[K, V]
|
|
76
|
+
# (Ruby yields `[key, value]` pairs as the element)
|
|
77
|
+
# - `Constant<Array>` / `Constant<Range>` / `Constant<Set>`
|
|
78
|
+
# → corresponding Constant element
|
|
79
|
+
#
|
|
80
|
+
# Receivers we cannot project (Top, Dynamic, unknown
|
|
81
|
+
# nominals, IO, …) decline so the RBS tier still answers
|
|
82
|
+
# — its element type is correct, only the index would
|
|
83
|
+
# widen to plain Integer.
|
|
84
|
+
def each_with_index_block_params(receiver)
|
|
85
|
+
element = element_type_of(receiver)
|
|
86
|
+
return nil if element.nil?
|
|
87
|
+
|
|
88
|
+
[element, Type::Combinator.non_negative_int]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
ELEMENT_BY_NOMINAL = {
|
|
92
|
+
"Array" => :nominal_unary_element,
|
|
93
|
+
"Set" => :nominal_unary_element,
|
|
94
|
+
"Range" => :nominal_unary_element,
|
|
95
|
+
"Hash" => :nominal_hash_pair_element
|
|
96
|
+
}.freeze
|
|
97
|
+
private_constant :ELEMENT_BY_NOMINAL
|
|
98
|
+
|
|
99
|
+
def element_type_of(receiver)
|
|
100
|
+
case receiver
|
|
101
|
+
when Type::Tuple then tuple_element(receiver)
|
|
102
|
+
when Type::HashShape then hash_shape_pair_element(receiver)
|
|
103
|
+
when Type::Nominal then nominal_element(receiver)
|
|
104
|
+
when Type::Constant then constant_element(receiver)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def tuple_element(tuple)
|
|
109
|
+
return nil if tuple.elements.empty?
|
|
110
|
+
return tuple.elements.first if tuple.elements.size == 1
|
|
111
|
+
|
|
112
|
+
Type::Combinator.union(*tuple.elements)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def hash_shape_pair_element(shape)
|
|
116
|
+
return nil if shape.pairs.empty?
|
|
117
|
+
|
|
118
|
+
key = Type::Combinator.union(*shape.pairs.keys.map { |k| Type::Combinator.constant_of(k) })
|
|
119
|
+
value = Type::Combinator.union(*shape.pairs.values)
|
|
120
|
+
Type::Combinator.tuple_of(key, value)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def nominal_element(nominal)
|
|
124
|
+
handler = ELEMENT_BY_NOMINAL[nominal.class_name]
|
|
125
|
+
return nil unless handler
|
|
126
|
+
|
|
127
|
+
send(handler, nominal)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def nominal_unary_element(nominal)
|
|
131
|
+
nominal.type_args.first
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def nominal_hash_pair_element(nominal)
|
|
135
|
+
key, value = nominal.type_args
|
|
136
|
+
return nil if key.nil? || value.nil?
|
|
137
|
+
|
|
138
|
+
Type::Combinator.tuple_of(key, value)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def constant_element(constant)
|
|
142
|
+
case constant.value
|
|
143
|
+
when Array
|
|
144
|
+
return nil if constant.value.empty?
|
|
145
|
+
|
|
146
|
+
Type::Combinator.union(*constant.value.map { |v| Type::Combinator.constant_of(v) })
|
|
147
|
+
when Range
|
|
148
|
+
range_constant_element(constant.value)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def range_constant_element(range)
|
|
153
|
+
beg = range.begin
|
|
154
|
+
en = range.end
|
|
155
|
+
return Type::Combinator.constant_of(beg) if beg.is_a?(Integer) && beg == en
|
|
156
|
+
|
|
157
|
+
if beg.is_a?(Integer) && en.is_a?(Integer)
|
|
158
|
+
upper = range.exclude_end? ? en - 1 : en
|
|
159
|
+
return build_index_range(beg, upper)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Mixed / non-integer ranges decline: the dispatcher
|
|
163
|
+
# falls through to RBS's element-type answer.
|
|
164
|
+
nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# `Constant<Integer>`, `IntegerRange`, and `Nominal[Integer]`
|
|
168
|
+
# all participate. Non-integer types (Float, String, …) and
|
|
169
|
+
# `Top`/`Dynamic` decline so the RBS tier answers.
|
|
170
|
+
def integer_rooted?(type)
|
|
171
|
+
case type
|
|
172
|
+
when Type::Constant then type.value.is_a?(Integer)
|
|
173
|
+
when Type::IntegerRange then true
|
|
174
|
+
when Type::Nominal then type.class_name == "Integer" && type.type_args.empty?
|
|
175
|
+
else false
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def lower_bound_of(type)
|
|
180
|
+
case type
|
|
181
|
+
when Type::Constant then type.value
|
|
182
|
+
when Type::IntegerRange then type.min
|
|
183
|
+
when Type::Nominal then Type::IntegerRange::NEG_INFINITY
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def upper_bound_of(type)
|
|
188
|
+
case type
|
|
189
|
+
when Type::Constant then type.value
|
|
190
|
+
when Type::IntegerRange then type.max
|
|
191
|
+
when Type::Nominal then Type::IntegerRange::POS_INFINITY
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Builds a `Constant`/`IntegerRange` from possibly-symbolic
|
|
196
|
+
# bounds. Vacuous ranges (lower > upper, indicating the
|
|
197
|
+
# iterator does not fire) collapse to `non_negative_int` so
|
|
198
|
+
# the body still type-checks against a sensible binding.
|
|
199
|
+
def build_index_range(lower, upper)
|
|
200
|
+
return Type::Combinator.non_negative_int if vacuous_range?(lower, upper)
|
|
201
|
+
return Type::Combinator.constant_of(lower) if lower.is_a?(Integer) && lower == upper
|
|
202
|
+
|
|
203
|
+
Type::Combinator.integer_range(lower, upper)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def vacuous_range?(lower, upper)
|
|
207
|
+
return false if lower == Type::IntegerRange::NEG_INFINITY
|
|
208
|
+
return false if upper == Type::IntegerRange::POS_INFINITY
|
|
209
|
+
|
|
210
|
+
lower > upper
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
@@ -53,13 +53,22 @@ module Rigor
|
|
|
53
53
|
overloads = method_definition.method_types
|
|
54
54
|
return nil if overloads.empty?
|
|
55
55
|
|
|
56
|
+
# `rigor:v1:param: <name> <refinement>` annotations on
|
|
57
|
+
# this method override the RBS-declared parameter type
|
|
58
|
+
# at the matching name. The map is consumed inside
|
|
59
|
+
# `accepts_param?` so overload selection sees the
|
|
60
|
+
# tighter type when filtering candidates by argument
|
|
61
|
+
# compatibility.
|
|
62
|
+
param_overrides = RbsExtended.param_type_override_map(method_definition)
|
|
63
|
+
|
|
56
64
|
match = find_matching_overload(
|
|
57
65
|
overloads,
|
|
58
66
|
arg_types: arg_types,
|
|
59
67
|
self_type: self_type,
|
|
60
68
|
instance_type: instance_type,
|
|
61
69
|
type_vars: type_vars,
|
|
62
|
-
block_required: block_required
|
|
70
|
+
block_required: block_required,
|
|
71
|
+
param_overrides: param_overrides
|
|
63
72
|
)
|
|
64
73
|
return match if match
|
|
65
74
|
return overloads.find { |mt| overload_has_block?(mt) } if block_required
|
|
@@ -76,7 +85,8 @@ module Rigor
|
|
|
76
85
|
private
|
|
77
86
|
|
|
78
87
|
# rubocop:disable Metrics/ParameterLists
|
|
79
|
-
def find_matching_overload(overloads, arg_types:, self_type:, instance_type:, type_vars:, block_required
|
|
88
|
+
def find_matching_overload(overloads, arg_types:, self_type:, instance_type:, type_vars:, block_required:,
|
|
89
|
+
param_overrides:)
|
|
80
90
|
overloads.find do |method_type|
|
|
81
91
|
next false if block_required && !OverloadSelector.overload_has_block?(method_type)
|
|
82
92
|
|
|
@@ -85,13 +95,15 @@ module Rigor
|
|
|
85
95
|
arg_types,
|
|
86
96
|
self_type: self_type,
|
|
87
97
|
instance_type: instance_type,
|
|
88
|
-
type_vars: type_vars
|
|
98
|
+
type_vars: type_vars,
|
|
99
|
+
param_overrides: param_overrides
|
|
89
100
|
)
|
|
90
101
|
end
|
|
91
102
|
end
|
|
92
103
|
# rubocop:enable Metrics/ParameterLists
|
|
93
104
|
|
|
94
|
-
|
|
105
|
+
# rubocop:disable Metrics/ParameterLists
|
|
106
|
+
def matches?(method_type, arg_types, self_type:, instance_type:, type_vars:, param_overrides:)
|
|
95
107
|
return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
|
|
96
108
|
|
|
97
109
|
fun = method_type.type
|
|
@@ -104,10 +116,12 @@ module Rigor
|
|
|
104
116
|
arg,
|
|
105
117
|
self_type: self_type,
|
|
106
118
|
instance_type: instance_type,
|
|
107
|
-
type_vars: type_vars
|
|
119
|
+
type_vars: type_vars,
|
|
120
|
+
param_overrides: param_overrides
|
|
108
121
|
)
|
|
109
122
|
end
|
|
110
123
|
end
|
|
124
|
+
# rubocop:enable Metrics/ParameterLists
|
|
111
125
|
|
|
112
126
|
# Slice 4 phase 2c does not pass keyword arguments through the
|
|
113
127
|
# call site (caller passes only positional `arg_types`). An
|
|
@@ -152,8 +166,9 @@ module Rigor
|
|
|
152
166
|
head
|
|
153
167
|
end
|
|
154
168
|
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
# rubocop:disable Metrics/ParameterLists
|
|
170
|
+
def accepts_param?(param, arg, self_type:, instance_type:, type_vars:, param_overrides:)
|
|
171
|
+
param_type = param_overrides[param.name] || RbsTypeTranslator.translate(
|
|
157
172
|
param.type,
|
|
158
173
|
self_type: self_type,
|
|
159
174
|
instance_type: instance_type,
|
|
@@ -162,6 +177,7 @@ module Rigor
|
|
|
162
177
|
result = param_type.accepts(arg, mode: :gradual)
|
|
163
178
|
result.yes? || result.maybe?
|
|
164
179
|
end
|
|
180
|
+
# rubocop:enable Metrics/ParameterLists
|
|
165
181
|
end
|
|
166
182
|
end
|
|
167
183
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "../../rbs_extended"
|
|
4
5
|
require_relative "../rbs_type_translator"
|
|
5
6
|
require_relative "overload_selector"
|
|
6
7
|
|
|
@@ -247,6 +248,9 @@ module Rigor
|
|
|
247
248
|
|
|
248
249
|
# rubocop:disable Metrics/ParameterLists
|
|
249
250
|
def translate_return_type(method_definition, class_name:, kind:, args:, type_vars:, block_type:)
|
|
251
|
+
override = RbsExtended.read_return_type_override(method_definition)
|
|
252
|
+
return override if override
|
|
253
|
+
|
|
250
254
|
instance_type = Type::Combinator.nominal_of(class_name)
|
|
251
255
|
self_type =
|
|
252
256
|
case kind
|