rigortype 0.1.2 → 0.1.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 +135 -31
- data/lib/rigor/analysis/check_rules.rb +10 -18
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +113 -0
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +72 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +139 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +200 -0
- data/lib/rigor/analysis/dependency_source_inference.rb +38 -0
- data/lib/rigor/analysis/diagnostic.rb +0 -2
- data/lib/rigor/analysis/fact_store.rb +11 -3
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/runner.rb +206 -6
- data/lib/rigor/builtins/imported_refinements.rb +360 -55
- data/lib/rigor/cache/descriptor.rb +59 -6
- data/lib/rigor/cache/store.rb +1 -1
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +173 -0
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_renderer.rb +1 -1
- data/lib/rigor/cli/type_scan_report.rb +2 -2
- data/lib/rigor/cli.rb +9 -1
- data/lib/rigor/configuration/dependencies.rb +235 -0
- data/lib/rigor/configuration.rb +45 -11
- data/lib/rigor/environment.rb +47 -4
- data/lib/rigor/flow_contribution/conflict.rb +2 -2
- data/lib/rigor/flow_contribution/element.rb +1 -1
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution/merge_result.rb +1 -1
- data/lib/rigor/flow_contribution/merger.rb +7 -3
- data/lib/rigor/flow_contribution.rb +2 -2
- data/lib/rigor/inference/block_parameter_binder.rb +0 -2
- data/lib/rigor/inference/coverage_scanner.rb +1 -1
- data/lib/rigor/inference/expression_typer.rb +67 -11
- data/lib/rigor/inference/fallback.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +0 -12
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +118 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +6 -11
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +0 -4
- data/lib/rigor/inference/method_dispatcher.rb +233 -2
- data/lib/rigor/inference/method_parameter_binder.rb +1 -3
- data/lib/rigor/inference/narrowing.rb +2 -4
- data/lib/rigor/inference/rbs_type_translator.rb +0 -2
- data/lib/rigor/inference/scope_indexer.rb +14 -9
- data/lib/rigor/inference/statement_evaluator.rb +70 -6
- data/lib/rigor/plugin/io_boundary.rb +0 -2
- data/lib/rigor/plugin/loader.rb +2 -2
- data/lib/rigor/plugin/manifest.rb +49 -7
- data/lib/rigor/plugin/registry.rb +11 -0
- data/lib/rigor/plugin/services.rb +1 -1
- data/lib/rigor/plugin/type_node_resolver.rb +52 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/reporter.rb +91 -0
- data/lib/rigor/rbs_extended.rb +131 -32
- data/lib/rigor/scope.rb +25 -8
- data/lib/rigor/sig_gen/classification.rb +36 -0
- data/lib/rigor/sig_gen/generator.rb +1048 -0
- data/lib/rigor/sig_gen/layout_index.rb +108 -0
- data/lib/rigor/sig_gen/method_candidate.rb +62 -0
- data/lib/rigor/sig_gen/observation_collector.rb +391 -0
- data/lib/rigor/sig_gen/observed_call.rb +62 -0
- data/lib/rigor/sig_gen/path_mapper.rb +116 -0
- data/lib/rigor/sig_gen/renderer.rb +157 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
- data/lib/rigor/sig_gen/write_result.rb +48 -0
- data/lib/rigor/sig_gen/writer.rb +530 -0
- data/lib/rigor/sig_gen.rb +25 -0
- data/lib/rigor/type/bound_method.rb +79 -0
- data/lib/rigor/type/combinator.rb +195 -2
- data/lib/rigor/type/constant.rb +13 -0
- data/lib/rigor/type/hash_shape.rb +0 -2
- data/lib/rigor/type/union.rb +20 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +62 -0
- data/lib/rigor/type_node/identifier.rb +30 -0
- data/lib/rigor/type_node/indexed_access.rb +41 -0
- data/lib/rigor/type_node/integer_literal.rb +29 -0
- data/lib/rigor/type_node/name_scope.rb +52 -0
- data/lib/rigor/type_node/resolver_chain.rb +56 -0
- data/lib/rigor/type_node/string_literal.rb +29 -0
- data/lib/rigor/type_node/symbol_literal.rb +28 -0
- data/lib/rigor/type_node/union.rb +42 -0
- data/lib/rigor/type_node.rb +29 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +2 -0
- data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
- data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
- data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
- data/sig/rigor/cli/diff_command.rbs +4 -0
- data/sig/rigor/cli/explain_command.rbs +4 -0
- data/sig/rigor/cli/sig_gen_command.rbs +4 -0
- data/sig/rigor/cli/type_scan_command.rbs +3 -0
- data/sig/rigor/environment.rbs +6 -2
- data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
- data/sig/rigor/inference/builtins.rbs +2 -0
- data/sig/rigor/plugin/access_denied_error.rbs +3 -0
- data/sig/rigor/plugin/base.rbs +6 -0
- data/sig/rigor/plugin/fact_store.rbs +11 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/load_error.rbs +6 -0
- data/sig/rigor/plugin/loader.rbs +20 -0
- data/sig/rigor/plugin/manifest.rbs +9 -0
- data/sig/rigor/plugin/registry.rbs +3 -0
- data/sig/rigor/plugin/services.rbs +3 -0
- data/sig/rigor/plugin/trust_policy.rbs +4 -0
- data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
- data/sig/rigor/plugin.rbs +8 -0
- data/sig/rigor/scope.rbs +4 -2
- data/sig/rigor/type.rbs +28 -6
- metadata +58 -1
|
@@ -28,7 +28,7 @@ module Rigor
|
|
|
28
28
|
role
|
|
29
29
|
].freeze
|
|
30
30
|
|
|
31
|
-
Element
|
|
31
|
+
class Element < Data.define(:target, :edge, :kind, :payload, :provenance)
|
|
32
32
|
def initialize(target:, edge:, kind:, payload:, provenance:)
|
|
33
33
|
unless ELEMENT_VALID_EDGES.include?(edge)
|
|
34
34
|
raise ArgumentError,
|
|
@@ -55,7 +55,7 @@ module Rigor
|
|
|
55
55
|
# land in the same merge bucket.
|
|
56
56
|
FACT_VALID_TARGET_KINDS = %i[parameter self].freeze
|
|
57
57
|
|
|
58
|
-
Fact
|
|
58
|
+
class Fact < Data.define(:target_kind, :target_name, :type, :negative)
|
|
59
59
|
def initialize(target_kind:, target_name:, type:, negative: false)
|
|
60
60
|
unless FACT_VALID_TARGET_KINDS.include?(target_kind)
|
|
61
61
|
raise ArgumentError,
|
|
@@ -42,7 +42,7 @@ module Rigor
|
|
|
42
42
|
!@conflicts.empty?
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def empty?
|
|
45
|
+
def empty?
|
|
46
46
|
@return_type.nil? && @truthy_facts.empty? && @falsey_facts.empty? &&
|
|
47
47
|
@post_return_facts.empty? && @mutations.empty? && @invalidations.empty? &&
|
|
48
48
|
@exceptional.nil? && @role_conformance.empty?
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "conflict"
|
|
4
|
+
require_relative "fact"
|
|
5
|
+
require_relative "merge_result"
|
|
6
|
+
|
|
3
7
|
module Rigor
|
|
4
8
|
class FlowContribution
|
|
5
9
|
# Composes any number of {FlowContribution} bundles into a
|
|
@@ -51,7 +55,7 @@ module Rigor
|
|
|
51
55
|
# In every conflict case the result keeps the higher-tier value
|
|
52
56
|
# for that slot, records a {Conflict} with both provenances, and
|
|
53
57
|
# continues processing the remaining slots / contributions.
|
|
54
|
-
module Merger
|
|
58
|
+
module Merger
|
|
55
59
|
AUTHORITY_TIERS = {
|
|
56
60
|
builtin: 0,
|
|
57
61
|
rbs_extended: 1,
|
|
@@ -108,7 +112,7 @@ module Rigor
|
|
|
108
112
|
fold_role_conformance(state, contribution)
|
|
109
113
|
end
|
|
110
114
|
|
|
111
|
-
def fold_return_type(state, contribution, tier)
|
|
115
|
+
def fold_return_type(state, contribution, tier)
|
|
112
116
|
incoming = contribution.return_type
|
|
113
117
|
return if incoming.nil?
|
|
114
118
|
|
|
@@ -198,7 +202,7 @@ module Rigor
|
|
|
198
202
|
end
|
|
199
203
|
end
|
|
200
204
|
|
|
201
|
-
def build_conflict(target:, edge:, kind:, reason:, provenances:, message:)
|
|
205
|
+
def build_conflict(target:, edge:, kind:, reason:, provenances:, message:)
|
|
202
206
|
Conflict.new(target: target, edge: edge, kind: kind, reason: reason,
|
|
203
207
|
provenances: provenances, message: message)
|
|
204
208
|
end
|
|
@@ -32,7 +32,7 @@ module Rigor
|
|
|
32
32
|
# `descriptor` is the {Rigor::Cache::Descriptor} this
|
|
33
33
|
# contribution attaches to (or `nil` when the contribution does
|
|
34
34
|
# not need its own cache slice).
|
|
35
|
-
Provenance
|
|
35
|
+
class Provenance < Data.define(:source_family, :plugin_id, :node, :descriptor)
|
|
36
36
|
def self.builtin
|
|
37
37
|
new(source_family: :builtin, plugin_id: nil, node: nil, descriptor: nil)
|
|
38
38
|
end
|
|
@@ -122,7 +122,7 @@ module Rigor
|
|
|
122
122
|
# | role_conformance | normal | role | (per-role target) |
|
|
123
123
|
#
|
|
124
124
|
# @return [Array<Element>]
|
|
125
|
-
def to_element_list # rubocop:disable Metrics/AbcSize
|
|
125
|
+
def to_element_list # rubocop:disable Metrics/AbcSize
|
|
126
126
|
elements = []
|
|
127
127
|
elements << element_for(:return, :normal, :return_type, return_type) unless return_type.nil?
|
|
128
128
|
Array(truthy_facts).each { |fact| elements << element_for(fact_target(fact), :truthy, :truthy_fact, fact) }
|
|
@@ -45,7 +45,6 @@ module Rigor
|
|
|
45
45
|
# scope MUST NOT observe them and the binder leaves them unbound.
|
|
46
46
|
#
|
|
47
47
|
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
48
|
-
# rubocop:disable Metrics/ClassLength
|
|
49
48
|
class BlockParameterBinder
|
|
50
49
|
# @param expected_param_types [Array<Rigor::Type>] positional block
|
|
51
50
|
# parameter types in order. Indices the binder cannot fill from
|
|
@@ -208,6 +207,5 @@ module Rigor
|
|
|
208
207
|
@expected_param_types[index] || Type::Combinator.untyped
|
|
209
208
|
end
|
|
210
209
|
end
|
|
211
|
-
# rubocop:enable Metrics/ClassLength
|
|
212
210
|
end
|
|
213
211
|
end
|
|
@@ -24,7 +24,7 @@ module Rigor
|
|
|
24
24
|
# hot inference path: it allocates a tracer per visited node and discards
|
|
25
25
|
# the inferred type values.
|
|
26
26
|
class CoverageScanner
|
|
27
|
-
Result
|
|
27
|
+
class Result < Data.define(:visits, :unrecognized, :events)
|
|
28
28
|
# @return [Integer] sum of all visits across node classes.
|
|
29
29
|
def visited_count
|
|
30
30
|
visits.values.sum
|
|
@@ -111,7 +111,21 @@ module Rigor
|
|
|
111
111
|
Prism::IndexOrWriteNode => :type_of_assignment_write,
|
|
112
112
|
Prism::IndexAndWriteNode => :type_of_assignment_write,
|
|
113
113
|
Prism::MultiWriteNode => :type_of_assignment_write,
|
|
114
|
+
# LHS-only target nodes (destructuring assignment, pattern matching,
|
|
115
|
+
# `for x in xs`, block parameter `|a, (b, c)|`). They have no value
|
|
116
|
+
# to extract — the type-of pass acknowledges the node class so the
|
|
117
|
+
# coverage scanner stops flagging it; binding the inner names back
|
|
118
|
+
# into the scope is the StatementEvaluator / MultiTargetBinder /
|
|
119
|
+
# BlockParameterBinder side's concern.
|
|
114
120
|
Prism::LocalVariableTargetNode => :type_of_non_value,
|
|
121
|
+
Prism::MultiTargetNode => :type_of_non_value,
|
|
122
|
+
Prism::InstanceVariableTargetNode => :type_of_non_value,
|
|
123
|
+
Prism::ClassVariableTargetNode => :type_of_non_value,
|
|
124
|
+
Prism::GlobalVariableTargetNode => :type_of_non_value,
|
|
125
|
+
Prism::ConstantTargetNode => :type_of_non_value,
|
|
126
|
+
Prism::ConstantPathTargetNode => :type_of_non_value,
|
|
127
|
+
Prism::CallTargetNode => :type_of_non_value,
|
|
128
|
+
Prism::IndexTargetNode => :type_of_non_value,
|
|
115
129
|
# Hashes and interpolation
|
|
116
130
|
Prism::HashNode => :type_of_hash,
|
|
117
131
|
Prism::KeywordHashNode => :type_of_hash,
|
|
@@ -931,7 +945,6 @@ module Rigor
|
|
|
931
945
|
# for the CallNode itself (the inner type_of calls already record
|
|
932
946
|
# their own fallbacks for unrecognised receivers/args, so the tracer
|
|
933
947
|
# captures both the immediate dispatch miss and the deeper cause).
|
|
934
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
935
948
|
def call_type_for(node)
|
|
936
949
|
receiver = call_receiver_type_for(node)
|
|
937
950
|
arg_types = call_arg_types(node)
|
|
@@ -1004,7 +1017,6 @@ module Rigor
|
|
|
1004
1017
|
|
|
1005
1018
|
fallback_for(node, family: :prism)
|
|
1006
1019
|
end
|
|
1007
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
1008
1020
|
|
|
1009
1021
|
# v0.0.2 #5 — re-types the body of a user-defined
|
|
1010
1022
|
# instance method with the call site's argument types
|
|
@@ -1160,9 +1172,20 @@ module Rigor
|
|
|
1160
1172
|
# when typing the body raises (defensive against malformed
|
|
1161
1173
|
# subtrees); the dispatcher then runs in its no-block-aware
|
|
1162
1174
|
# path.
|
|
1175
|
+
#
|
|
1176
|
+
# ADR-14 gap-#3 (d): a `Prism::BlockArgumentNode` carrying
|
|
1177
|
+
# `&:symbol` (the Symbol#to_proc shorthand) is treated as
|
|
1178
|
+
# a block. The block's return type is computed by
|
|
1179
|
+
# dispatching `:symbol` on the expected block param type
|
|
1180
|
+
# (per `Symbol#to_proc`'s `{ |x| x.symbol }` semantics).
|
|
1181
|
+
# A precise inner dispatch produces the right return; any
|
|
1182
|
+
# failure step falls back to `Dynamic[Top]` so the
|
|
1183
|
+
# dispatcher still SEES a block — selecting the block-
|
|
1184
|
+
# bearing overload of e.g. `Hash#transform_values` over
|
|
1185
|
+
# the no-block overload that returns `Enumerator`.
|
|
1163
1186
|
def block_return_type_for(call_node, receiver_type, arg_types)
|
|
1164
|
-
|
|
1165
|
-
return nil
|
|
1187
|
+
block_arg = call_node.block
|
|
1188
|
+
return nil if block_arg.nil?
|
|
1166
1189
|
return nil if receiver_type.nil?
|
|
1167
1190
|
|
|
1168
1191
|
expected = MethodDispatcher.expected_block_param_types(
|
|
@@ -1171,13 +1194,50 @@ module Rigor
|
|
|
1171
1194
|
arg_types: arg_types,
|
|
1172
1195
|
environment: scope.environment
|
|
1173
1196
|
)
|
|
1174
|
-
|
|
1175
|
-
block_scope = bindings.reduce(scope) { |acc, (name, type)| acc.with_local(name, type) }
|
|
1176
|
-
type_block_body(block_node, block_scope)
|
|
1197
|
+
block_return_for(block_arg, expected)
|
|
1177
1198
|
rescue StandardError
|
|
1178
1199
|
nil
|
|
1179
1200
|
end
|
|
1180
1201
|
|
|
1202
|
+
def block_return_for(block_arg, expected)
|
|
1203
|
+
case block_arg
|
|
1204
|
+
when Prism::BlockNode
|
|
1205
|
+
bindings = BlockParameterBinder.new(expected_param_types: expected).bind(block_arg)
|
|
1206
|
+
block_scope = bindings.reduce(scope) { |acc, (name, type)| acc.with_local(name, type) }
|
|
1207
|
+
type_block_body(block_arg, block_scope)
|
|
1208
|
+
when Prism::BlockArgumentNode
|
|
1209
|
+
symbol_block_return_type(block_arg, expected)
|
|
1210
|
+
end
|
|
1211
|
+
end
|
|
1212
|
+
|
|
1213
|
+
# `&:symbol` desugars to a one-arg Proc that dispatches
|
|
1214
|
+
# `symbol` against its argument. When the param type is
|
|
1215
|
+
# known and the resulting inner dispatch is precise,
|
|
1216
|
+
# this returns the precise carrier; otherwise it
|
|
1217
|
+
# returns `Dynamic[Top]` (still non-nil) so the outer
|
|
1218
|
+
# dispatcher selects the block-bearing overload.
|
|
1219
|
+
# `&proc_local` / `&method(:foo)` and friends — anything
|
|
1220
|
+
# not a bare SymbolNode — still resolve to
|
|
1221
|
+
# `Dynamic[Top]` for the same block-presence signal.
|
|
1222
|
+
def symbol_block_return_type(block_arg, expected_param_types)
|
|
1223
|
+
expression = block_arg.expression
|
|
1224
|
+
return dynamic_top unless expression.is_a?(Prism::SymbolNode)
|
|
1225
|
+
|
|
1226
|
+
param_type = expected_param_types&.first
|
|
1227
|
+
return dynamic_top if param_type.nil?
|
|
1228
|
+
|
|
1229
|
+
result = MethodDispatcher.dispatch(
|
|
1230
|
+
receiver_type: param_type,
|
|
1231
|
+
method_name: expression.unescaped.to_sym,
|
|
1232
|
+
arg_types: [],
|
|
1233
|
+
block_type: nil,
|
|
1234
|
+
environment: scope.environment,
|
|
1235
|
+
call_node: block_arg,
|
|
1236
|
+
scope: scope
|
|
1237
|
+
)
|
|
1238
|
+
result || dynamic_top
|
|
1239
|
+
end
|
|
1240
|
+
|
|
1181
1241
|
def type_block_body(block_node, block_scope)
|
|
1182
1242
|
body = block_node.body
|
|
1183
1243
|
return Type::Combinator.constant_of(nil) if body.nil?
|
|
@@ -1213,7 +1273,6 @@ module Rigor
|
|
|
1213
1273
|
PER_ELEMENT_RANGE_LIMIT = 8
|
|
1214
1274
|
private_constant :PER_ELEMENT_RANGE_LIMIT
|
|
1215
1275
|
|
|
1216
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1217
1276
|
def try_per_element_block_fold(call_node, receiver_type)
|
|
1218
1277
|
return nil unless PER_ELEMENT_TUPLE_METHODS.include?(call_node.name)
|
|
1219
1278
|
return nil if find_family_with_args?(call_node)
|
|
@@ -1231,7 +1290,6 @@ module Rigor
|
|
|
1231
1290
|
|
|
1232
1291
|
assemble_per_element_result(call_node.name, per_position, element_types)
|
|
1233
1292
|
end
|
|
1234
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1235
1293
|
|
|
1236
1294
|
# Returns the per-position element types for a finite,
|
|
1237
1295
|
# statically-known receiver shape — or nil when the
|
|
@@ -1254,7 +1312,6 @@ module Rigor
|
|
|
1254
1312
|
end
|
|
1255
1313
|
end
|
|
1256
1314
|
|
|
1257
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
1258
1315
|
def constant_range_elements(value)
|
|
1259
1316
|
return nil unless value.is_a?(Range)
|
|
1260
1317
|
return nil unless value.begin.is_a?(Integer) && value.end.is_a?(Integer)
|
|
@@ -1264,7 +1321,6 @@ module Rigor
|
|
|
1264
1321
|
|
|
1265
1322
|
value.to_a.map { |v| Type::Combinator.constant_of(v) }
|
|
1266
1323
|
end
|
|
1267
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
1268
1324
|
|
|
1269
1325
|
# `index(value)` and `find_index(value)` carry a positional
|
|
1270
1326
|
# argument and search by `==` rather than running the block.
|
|
@@ -20,7 +20,7 @@ module Rigor
|
|
|
20
20
|
# - inner_type: the Rigor::Type returned to the caller (currently
|
|
21
21
|
# always Dynamic[Top]; later slices may carry richer fallback
|
|
22
22
|
# types).
|
|
23
|
-
Fallback
|
|
23
|
+
class Fallback < Data.define(:node_class, :location, :family, :inner_type)
|
|
24
24
|
def initialize(node_class:, location:, family:, inner_type:)
|
|
25
25
|
raise ArgumentError, "node_class must be a Class, got #{node_class.class}" unless node_class.is_a?(Class)
|
|
26
26
|
|
|
@@ -37,7 +37,7 @@ module Rigor
|
|
|
37
37
|
# tuple — element-wise block re-evaluation against
|
|
38
38
|
# `Constant<Array>` receivers (the `map` / `filter_map` /
|
|
39
39
|
# `flat_map` precision tier) is reserved for a later slice.
|
|
40
|
-
module BlockFolding
|
|
40
|
+
module BlockFolding
|
|
41
41
|
module_function
|
|
42
42
|
|
|
43
43
|
FILTER_KEEP_ON_TRUTHY = Set[:select, :filter, :take_while].freeze
|
|
@@ -69,7 +69,6 @@ module Rigor
|
|
|
69
69
|
# the call's block. `nil` means "no block at the call site"
|
|
70
70
|
# and disqualifies every rule here.
|
|
71
71
|
# @return [Rigor::Type, nil]
|
|
72
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
73
72
|
def try_fold(receiver:, method_name:, args:, block_type:)
|
|
74
73
|
return nil if receiver.nil? || block_type.nil?
|
|
75
74
|
|
|
@@ -86,7 +85,6 @@ module Rigor
|
|
|
86
85
|
fold_count(receiver, truthiness, args)
|
|
87
86
|
end
|
|
88
87
|
end
|
|
89
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
90
88
|
|
|
91
89
|
def filter_method?(method_name)
|
|
92
90
|
FILTER_KEEP_ON_TRUTHY.include?(method_name) ||
|
|
@@ -154,7 +152,7 @@ module Rigor
|
|
|
154
152
|
end
|
|
155
153
|
|
|
156
154
|
# @return [:always_true, :always_false, :bool, nil]
|
|
157
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
155
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
158
156
|
def predicate_decision(method_name, truthiness, emptiness)
|
|
159
157
|
case method_name
|
|
160
158
|
when :all?
|
|
@@ -177,7 +175,7 @@ module Rigor
|
|
|
177
175
|
:bool
|
|
178
176
|
end
|
|
179
177
|
end
|
|
180
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
178
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
181
179
|
|
|
182
180
|
def bool_union
|
|
183
181
|
Type::Combinator.union(
|
|
@@ -149,7 +149,6 @@ module Rigor
|
|
|
149
149
|
# and runs the format. Symbol keys are kept as
|
|
150
150
|
# Symbols (matching Ruby's `%{key}` resolution).
|
|
151
151
|
# Anything else declines so the RBS tier widens.
|
|
152
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
153
152
|
def try_fold_string_format(receiver, method_name, args)
|
|
154
153
|
return nil unless method_name == :%
|
|
155
154
|
return nil unless args.size == 1
|
|
@@ -166,7 +165,6 @@ module Rigor
|
|
|
166
165
|
rescue StandardError
|
|
167
166
|
nil
|
|
168
167
|
end
|
|
169
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
170
168
|
|
|
171
169
|
def format_argument_value(arg)
|
|
172
170
|
case arg
|
|
@@ -275,7 +273,6 @@ module Rigor
|
|
|
275
273
|
[result]
|
|
276
274
|
end
|
|
277
275
|
|
|
278
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
279
276
|
def try_fold_unary_set(receiver_values, method_name)
|
|
280
277
|
range_lift = try_fold_range_constant_unary(receiver_values, method_name)
|
|
281
278
|
return range_lift if range_lift
|
|
@@ -299,8 +296,6 @@ module Rigor
|
|
|
299
296
|
end
|
|
300
297
|
build_constant_type(results, source: receiver_values)
|
|
301
298
|
end
|
|
302
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
303
|
-
|
|
304
299
|
# v0.0.7 — `Constant<Range>#to_a` and the no-arg
|
|
305
300
|
# `first` / `last` / `min` / `max` short-circuit through a
|
|
306
301
|
# Range-specific arm that catalog dispatch cannot reach:
|
|
@@ -353,7 +348,6 @@ module Rigor
|
|
|
353
348
|
Type::Combinator.constant_of(edge == :first ? values.first : values.last)
|
|
354
349
|
end
|
|
355
350
|
|
|
356
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
357
351
|
def try_fold_binary_set(receiver_values, method_name, arg_values)
|
|
358
352
|
string_lift = try_fold_string_array_binary(receiver_values, method_name, arg_values)
|
|
359
353
|
return string_lift if string_lift
|
|
@@ -369,8 +363,6 @@ module Rigor
|
|
|
369
363
|
end
|
|
370
364
|
build_constant_type(results, source: receiver_values + arg_values)
|
|
371
365
|
end
|
|
372
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
373
|
-
|
|
374
366
|
# v0.0.7 — `Constant<String>#chars` / `bytes` / `lines` /
|
|
375
367
|
# `split` (no-arg) return a Ruby Array of foldable
|
|
376
368
|
# scalars; `foldable_constant_value?` rejects Array
|
|
@@ -425,7 +417,6 @@ module Rigor
|
|
|
425
417
|
nil
|
|
426
418
|
end
|
|
427
419
|
|
|
428
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
429
420
|
def try_fold_pathname_binary(receiver_values, method_name, arg_values)
|
|
430
421
|
return nil unless PATHNAME_PURE_BINARY.include?(method_name)
|
|
431
422
|
return nil unless receiver_values.size == 1 && arg_values.size == 1
|
|
@@ -442,7 +433,6 @@ module Rigor
|
|
|
442
433
|
rescue StandardError
|
|
443
434
|
nil
|
|
444
435
|
end
|
|
445
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
446
436
|
|
|
447
437
|
def try_fold_string_array_unary(receiver_values, method_name)
|
|
448
438
|
return nil unless STRING_ARRAY_UNARY_METHODS.include?(method_name)
|
|
@@ -459,7 +449,6 @@ module Rigor
|
|
|
459
449
|
# `Constant<String>#split(arg)` / `#scan(arg)` — lift the
|
|
460
450
|
# Array result to a Tuple when both sides are statically
|
|
461
451
|
# known and the cardinality fits.
|
|
462
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
463
452
|
def try_fold_string_array_binary(receiver_values, method_name, arg_values)
|
|
464
453
|
return nil unless STRING_ARRAY_BINARY_METHODS.include?(method_name)
|
|
465
454
|
return nil unless receiver_values.size == 1 && arg_values.size == 1
|
|
@@ -473,7 +462,6 @@ module Rigor
|
|
|
473
462
|
rescue StandardError
|
|
474
463
|
nil
|
|
475
464
|
end
|
|
476
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
477
465
|
|
|
478
466
|
def lift_array_result(result)
|
|
479
467
|
return nil unless result.is_a?(Array)
|
|
@@ -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
|
|
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)
|
|
@@ -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:, **)
|
|
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,118 @@
|
|
|
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
|
+
MethodDispatcher.dispatch(
|
|
80
|
+
receiver_type: receiver.receiver_type,
|
|
81
|
+
method_name: receiver.method_name,
|
|
82
|
+
arg_types: args,
|
|
83
|
+
block_type: block_type,
|
|
84
|
+
environment: environment,
|
|
85
|
+
call_node: call_node,
|
|
86
|
+
scope: scope
|
|
87
|
+
) || Type::Combinator.untyped
|
|
88
|
+
end
|
|
89
|
+
# `Method#call` / `Method#()` and `Method#[]` are the
|
|
90
|
+
# invocation entry points on the `Method` API; the
|
|
91
|
+
# alias `===` is also `call` semantically but is more
|
|
92
|
+
# commonly used as a case-equality predicate, so we
|
|
93
|
+
# do NOT fold through it (the case/when narrowing path
|
|
94
|
+
# already special-cases `===` for branch typing).
|
|
95
|
+
BACKWARD_METHOD_NAMES = %i[call []].freeze
|
|
96
|
+
private_constant :BACKWARD_METHOD_NAMES
|
|
97
|
+
|
|
98
|
+
def backward_method?(method_name)
|
|
99
|
+
BACKWARD_METHOD_NAMES.include?(method_name)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# `Object#method` accepts both Symbol and String at
|
|
103
|
+
# runtime (the latter coerced via `to_sym`). The
|
|
104
|
+
# `Constant<String>` form is rare in production code
|
|
105
|
+
# but cheap to support and matches Ruby's documented
|
|
106
|
+
# contract.
|
|
107
|
+
def symbol_name_of(arg)
|
|
108
|
+
return nil unless arg.is_a?(Type::Constant)
|
|
109
|
+
|
|
110
|
+
case arg.value
|
|
111
|
+
when Symbol then arg.value
|
|
112
|
+
when String then arg.value.to_sym
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -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
|
|
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
|
-
|
|
68
|
-
|
|
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
|
|
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
|
|
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
|