rigortype 0.4.1 → 0.4.2
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 +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/03-configuration.md +1 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +289 -57
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +3 -1
- data/lib/rigor/analysis/incremental_session.rb +14 -1
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
- data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +34 -3
- data/lib/rigor/analysis/worker_session.rb +7 -4
- data/lib/rigor/cache/incremental_snapshot.rb +22 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +13 -7
- data/lib/rigor/cli.rb +10 -2
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +46 -2
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +283 -14
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment.rb +21 -3
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +215 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +259 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +687 -60
- data/lib/rigor/inference/statement_evaluator.rb +71 -20
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/mutation_cache.rb +1 -1
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +189 -10
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +55 -5
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +27 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- metadata +66 -1
|
@@ -5,8 +5,11 @@ require_relative "../acceptance"
|
|
|
5
5
|
require_relative "../rbs_type_translator"
|
|
6
6
|
require_relative "alias_strict_nominals"
|
|
7
7
|
require_relative "facet_distribution"
|
|
8
|
+
require_relative "imprecise_argument"
|
|
9
|
+
require_relative "keyword_arguments"
|
|
8
10
|
require_relative "proven_overload"
|
|
9
11
|
require_relative "receiver_affinity"
|
|
12
|
+
require_relative "splat_arity"
|
|
10
13
|
|
|
11
14
|
module Rigor
|
|
12
15
|
module Inference
|
|
@@ -14,9 +17,14 @@ module Rigor
|
|
|
14
17
|
# Picks the RBS overload that should answer a call given the caller's actual argument types. Slice 4
|
|
15
18
|
# phase 2c shape (with the v0.1.2 interface-strictness preference layered on top):
|
|
16
19
|
#
|
|
17
|
-
# 1. Filter overloads by positional arity (required, optional and rest_positionals are honored
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
+
# 1. Filter overloads by positional arity (required, optional and rest_positionals are honored).
|
|
21
|
+
# Keyword arguments (#1727): when the call's last argument is a keyword hash (`keywords_last:`, which
|
|
22
|
+
# only the caller can tell from the AST) and an overload declares keywords, that argument is matched
|
|
23
|
+
# against the overload's keywords rather than as a positional `Hash` — every required keyword
|
|
24
|
+
# present, every key declared, every value accepted. An overload that declares no keywords still
|
|
25
|
+
# reads it as a trailing positional `Hash`, as Ruby passes it, but never in place of a keyword overload that
|
|
26
|
+
# takes it (#1800, `KeywordArguments.prefer_takers`). A call without keywords skips an overload that requires
|
|
27
|
+
# one.
|
|
20
28
|
# 1a. **Pass 0 — proven, in declared order (#1344).** With only plain-value arguments, the first
|
|
21
29
|
# overload they do not rule out wins outright when each of its params names its argument's own
|
|
22
30
|
# class and accepts it with a `yes`; every later pass reads the receiver-affinity order instead.
|
|
@@ -44,7 +52,8 @@ module Rigor
|
|
|
44
52
|
module OverloadSelector
|
|
45
53
|
module_function
|
|
46
54
|
|
|
47
|
-
# `ALIAS_STRICT_NOMINALS`, the alias-strict pass's table, lives in `alias_strict_nominals.rb
|
|
55
|
+
# `ALIAS_STRICT_NOMINALS`, the alias-strict pass's table, lives in `alias_strict_nominals.rb`, with the pass's
|
|
56
|
+
# per-parameter test (`alias_param_accepts?`).
|
|
48
57
|
|
|
49
58
|
# @param arg_types — caller-provided types in positional order. Empty when
|
|
50
59
|
# there are no arguments.
|
|
@@ -70,32 +79,48 @@ module Rigor
|
|
|
70
79
|
# position, not by types — `[true] * n` with an untyped `n` pinned `Array#*(string) -> String`
|
|
71
80
|
# and answered a wrong precise type the runtime can contradict. The caller unions the candidates'
|
|
72
81
|
# returns, which contains the truth whichever overload the runtime takes. The member-wise reading of a
|
|
73
|
-
# two-member `Dynamic` facet (#1350, `FacetDistribution`) returns one candidate per member the same way
|
|
82
|
+
# two-member `Dynamic` facet (#1350, `FacetDistribution`) returns one candidate per member the same way, and
|
|
83
|
+
# where that reading falls back to the arguments as given, every overload they gradually match (#1782).
|
|
74
84
|
# Every other path (strict, alias-resolved, typed-gradual, arity fallback) yields exactly one candidate,
|
|
75
85
|
# and `select` (`member_wise: false`) keeps its historical single answer.
|
|
76
86
|
#
|
|
77
87
|
# @return matching overloads; empty when the definition declares none.
|
|
78
88
|
# rubocop:disable-next Metrics/ParameterLists -- the selection keywords plus the member-wise switch.
|
|
89
|
+
# @param keywords_last — the call's last argument is a keyword hash (`f(a, k: 1)`, not `f(a, { k: 1 })`).
|
|
90
|
+
# @param splats — the indices of `arg_types` that are splat arguments (`f(*xs)`), or nil (#1801).
|
|
79
91
|
def select_candidates(definition, arg_types:, self_type:, instance_type:, type_vars: {},
|
|
80
|
-
block_required: false, environment: nil, member_wise: true
|
|
92
|
+
block_required: false, environment: nil, member_wise: true, keywords_last: false,
|
|
93
|
+
splats: nil)
|
|
81
94
|
unless FacetDistribution.faceted?(arg_types)
|
|
82
95
|
return select_declared(definition, arg_types, self_type, instance_type, type_vars, block_required,
|
|
83
|
-
environment, false)
|
|
96
|
+
environment, false, keywords_last, splats)
|
|
84
97
|
end
|
|
85
98
|
|
|
86
99
|
FacetDistribution.select(arg_types, definition, member_wise:, environment:) do |args, member|
|
|
87
|
-
|
|
100
|
+
# Issue #1782 — the fallback (`member` false) reads the arguments as given, where the bare `Dynamic`
|
|
101
|
+
# gradually matches every arm and the strict pass's pick is the first by position, so the member-wise answer
|
|
102
|
+
# is every gradual match (`:joined`), as the block probe reads it (#1750); the caller joins their returns.
|
|
103
|
+
select_declared(definition, args, self_type, instance_type, type_vars, block_required, environment,
|
|
104
|
+
member || (member_wise && :joined), keywords_last, splats)
|
|
88
105
|
end
|
|
89
106
|
end
|
|
90
107
|
|
|
91
108
|
# The selection proper, positional so the hot path forwards no keyword hash. A member-wise call (`member`, see
|
|
92
|
-
# `FacetDistribution.select`) answers only a genuine match, never the first-overload fallback.
|
|
109
|
+
# `FacetDistribution.select`) answers only a genuine match, never the first-overload fallback. `member` set to
|
|
110
|
+
# `:gradual` answers every overload the arguments gradually match instead (#1750): where
|
|
111
|
+
# `FacetDistribution.select` falls back to the arguments as given, a bare `Dynamic` positional gradually
|
|
112
|
+
# matches every arm and the strict pass's single pick is decided by position, so the block-parameter probe
|
|
113
|
+
# binds only what all of them agree on. `:joined` answers the same matches, but where none matches it keeps the
|
|
114
|
+
# first-overload fallback, as the return path's reading of the arguments as given always has (#1782).
|
|
93
115
|
# rubocop:disable-next Metrics/ParameterLists -- the selection inputs plus the member-wise flag.
|
|
94
116
|
def select_declared(method_definition, arg_types, self_type, instance_type, type_vars, block_required,
|
|
95
|
-
environment, member)
|
|
117
|
+
environment, member, keywords_last, splats = nil)
|
|
96
118
|
declared = method_definition.method_types
|
|
97
119
|
return [] if declared.empty?
|
|
98
120
|
|
|
121
|
+
# A keyword hash only some overload takes as keywords is a plain positional `Hash` otherwise (#1737).
|
|
122
|
+
keywords_last &&= KeywordArguments.any_declares?(declared)
|
|
123
|
+
|
|
99
124
|
# `rigor:v1:param: <name> <refinement>` annotations on this method override the RBS-declared
|
|
100
125
|
# parameter type at the matching name. The map is consumed inside `accepts_param?` so overload
|
|
101
126
|
# selection sees the tighter type when filtering candidates by argument compatibility.
|
|
@@ -110,12 +135,17 @@ module Rigor
|
|
|
110
135
|
|
|
111
136
|
# One keyword bundle for the pass pipeline (see `run_selection_passes`); a block retry rebuilds it
|
|
112
137
|
# with the block flag cleared. Built once and passed positionally -- the previous lambda plus a
|
|
113
|
-
# `**shared` splat per pass allocated three objects per selection (#775).
|
|
138
|
+
# `**shared` splat per pass allocated three objects per selection (#775). Keep it at eight keys or fewer:
|
|
139
|
+
# CRuby embeds a Hash that small, and a ninth key allocates a table on every selection (the RBS loader is
|
|
140
|
+
# read from `environment` rather than carried as a key of its own, #1727).
|
|
114
141
|
shared = { arg_types: arg_types, self_type: self_type, instance_type: instance_type,
|
|
115
142
|
type_vars: type_vars, block_required: block_required, param_overrides: param_overrides,
|
|
116
|
-
|
|
143
|
+
environment: environment, keywords_last: keywords_last }
|
|
144
|
+
# Only a call with a splat pays for a ninth key (#1801).
|
|
145
|
+
shared[:splats] = splats if splats
|
|
117
146
|
|
|
118
|
-
|
|
147
|
+
gradual = member.is_a?(Symbol) # `:gradual` or `:joined`
|
|
148
|
+
matches = run_selection_passes(declared, overloads, shared, gradual)
|
|
119
149
|
return matches unless matches.empty?
|
|
120
150
|
|
|
121
151
|
# A block at the call site that no block-declaring overload matched: Ruby ignores a block handed
|
|
@@ -124,10 +154,10 @@ module Rigor
|
|
|
124
154
|
# `define_command(:x) do … end` against `def define_command: (Symbol) -> Symbol`) degraded to
|
|
125
155
|
# `Dynamic[Top]` — and on a self-send suppressed the whole method's return type.
|
|
126
156
|
if block_required
|
|
127
|
-
matches = run_selection_passes(declared, overloads, shared.merge(block_required: false))
|
|
157
|
+
matches = run_selection_passes(declared, overloads, shared.merge(block_required: false), gradual)
|
|
128
158
|
return matches unless matches.empty?
|
|
129
159
|
end
|
|
130
|
-
return [] if member
|
|
160
|
+
return [] if member && member != :joined
|
|
131
161
|
|
|
132
162
|
# No (usable) block at the call site: prefer an overload that does not REQUIRE a block over
|
|
133
163
|
# `overloads.first`. Methods like `Array#filter` / `Enumerable#map` declare the block-bearing
|
|
@@ -153,7 +183,7 @@ module Rigor
|
|
|
153
183
|
private
|
|
154
184
|
|
|
155
185
|
# Overload search after pass 0 (`find_proven_overload`):
|
|
156
|
-
# - Pass 1 (strict): skipped when any arg is imprecise (`
|
|
186
|
+
# - Pass 1 (strict): skipped when any arg is imprecise (`ImpreciseArgument.imprecise?`), because gradual
|
|
157
187
|
# acceptance against an untyped arg accepts every param indiscriminately and would let pass 1
|
|
158
188
|
# lock in an arbitrary strict overload (e.g. `Regexp#=~(nil) -> nil` over the
|
|
159
189
|
# `(::interned?) -> Integer?` overload).
|
|
@@ -172,25 +202,39 @@ module Rigor
|
|
|
172
202
|
# order, the strict pass let the `bigdecimal` reopen's `(BigDecimal) -> BigDecimal` arm, which it
|
|
173
203
|
# matches on a `maybe`, take `Integer#+` of a `bot`, a `Dynamic[Integer | Float | …]` or an
|
|
174
204
|
# unloadable class (612 call sites across the survey corpus).
|
|
175
|
-
def run_selection_passes(declared, overloads, shared)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
def run_selection_passes(declared, overloads, shared, gradual = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
206
|
+
matches = run_passes(declared, overloads, shared, gradual)
|
|
207
|
+
return matches unless shared[:keywords_last]
|
|
208
|
+
|
|
209
|
+
# Issue #1800 — never a positional reader of the keyword hash in place of a keyword overload that takes it.
|
|
210
|
+
KeywordArguments.prefer_takers(declared, matches) do |takers, strict|
|
|
211
|
+
find_matching_overload(takers, shared, strict:)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def run_passes(declared, overloads, shared, gradual)
|
|
216
|
+
# The `:gradual` list (#1750) skips straight to pass 2's every-match answer, stand-ins included.
|
|
217
|
+
unless gradual
|
|
218
|
+
# Unreordered, pass 0 can only pick what the strict pass picks, so it is skipped (#1344's allocations).
|
|
219
|
+
proven = find_proven_overload(declared, shared) unless overloads.equal?(declared)
|
|
220
|
+
strict = proven || find_matching_overload(overloads, shared, strict: true)
|
|
221
|
+
return strict unless strict.empty?
|
|
222
|
+
|
|
223
|
+
alias_hit = find_matching_overload_via_aliases(overloads, shared)
|
|
224
|
+
return [alias_hit] if alias_hit
|
|
225
|
+
end
|
|
185
226
|
|
|
186
227
|
# Pass 2, array-valued. With every argument carrying real type information the first gradual
|
|
187
228
|
# match keeps its historical single-winner contract. With an imprecise argument in play the
|
|
188
229
|
# matches are indistinguishable by types — position alone would pick — so ALL of them come back
|
|
189
230
|
# and the dispatch layer unions their returns (#521).
|
|
190
231
|
matches = find_matching_overload(overloads, shared, strict: false)
|
|
191
|
-
|
|
232
|
+
every_match = gradual || ImpreciseArgument.any_in?(shared[:arg_types], shared[:keywords_last])
|
|
233
|
+
return matches.first(1) unless every_match
|
|
192
234
|
|
|
193
|
-
|
|
235
|
+
# Issue #1675 — and every overload the arguments' untyped parts reach on their own, after the
|
|
236
|
+
# whole-argument matches so the singular `select` keeps its answer.
|
|
237
|
+
ImpreciseArgument.with_stand_ins(matches, shared) { find_matching_overload(overloads, it, strict: false) }
|
|
194
238
|
end
|
|
195
239
|
|
|
196
240
|
# Pass 0. With every argument a plain value (a `Constant`, or a `Nominal` with no type arguments), the
|
|
@@ -207,7 +251,7 @@ module Rigor
|
|
|
207
251
|
|
|
208
252
|
index = declared.index { |mt| engages_block_shape?(mt, shared[:block_required]) && matches?(mt, shared) }
|
|
209
253
|
first = index && declared[index]
|
|
210
|
-
proven = first && strictly_typed_params?(first,
|
|
254
|
+
proven = first && strictly_typed_params?(first, shared) && matches?(first, shared, strict: :proven)
|
|
211
255
|
[first] if proven && !ProvenOverload.contested?(declared, index, args, shared[:environment])
|
|
212
256
|
end
|
|
213
257
|
|
|
@@ -216,10 +260,10 @@ module Rigor
|
|
|
216
260
|
private_constant :NO_MATCH
|
|
217
261
|
|
|
218
262
|
# `shared` is the keyword bundle `select_candidates` assembled (arg_types, self_type, instance_type,
|
|
219
|
-
# type_vars, block_required, param_overrides,
|
|
263
|
+
# type_vars, block_required, param_overrides, environment, keywords_last).
|
|
220
264
|
def find_matching_overload(overloads, shared, strict:)
|
|
221
265
|
arg_types = shared[:arg_types]
|
|
222
|
-
return NO_MATCH if strict &&
|
|
266
|
+
return NO_MATCH if strict && ImpreciseArgument.any_in?(arg_types, shared[:keywords_last])
|
|
223
267
|
|
|
224
268
|
block_required = shared[:block_required]
|
|
225
269
|
# Strict keeps its historical first-match short-circuit (a dispatch hot path); the gradual
|
|
@@ -227,8 +271,8 @@ module Rigor
|
|
|
227
271
|
if strict
|
|
228
272
|
found = overloads.find do |method_type|
|
|
229
273
|
engages_block_shape?(method_type, block_required) &&
|
|
230
|
-
strictly_typed_params?(method_type,
|
|
231
|
-
matches?(method_type, shared, strict:
|
|
274
|
+
strictly_typed_params?(method_type, shared) &&
|
|
275
|
+
matches?(method_type, shared, strict: strict)
|
|
232
276
|
end
|
|
233
277
|
return found ? [found] : NO_MATCH
|
|
234
278
|
end
|
|
@@ -247,38 +291,26 @@ module Rigor
|
|
|
247
291
|
!OverloadSelector.overload_requires_block?(method_type)
|
|
248
292
|
end
|
|
249
293
|
|
|
250
|
-
# Treats the literal `untyped` carrier (`Dynamic[Top]`) as too imprecise to drive a strict-pass
|
|
251
|
-
# match. Other `Dynamic`-wrapped types with a concrete static facet carry enough information to
|
|
252
|
-
# pick a sensible overload.
|
|
253
|
-
def untyped_arg?(type) = type.is_a?(Type::Dynamic) && type.static_facet.is_a?(Type::Top)
|
|
254
|
-
|
|
255
|
-
# Issue #1021 — the strict and alias passes must not discriminate on an argument whose type
|
|
256
|
-
# cannot rule an overload out: the bare untyped carrier, or a union with an untyped member. That
|
|
257
|
-
# member may reach any overload at runtime, so a strict match is decided by the union's other
|
|
258
|
-
# members alone — `Dynamic[top] | nil` pinned `Regexp#match?(nil) -> false` and typed a live
|
|
259
|
-
# predicate as the literal `false`. The gradual pass still accepts against the whole union, so
|
|
260
|
-
# `Dynamic[top] | nil` keeps both `match?` overloads and the #521 join answers `Dynamic[bool]`.
|
|
261
|
-
# A `Dynamic` with a concrete static facet stays out: its facet discriminates.
|
|
262
|
-
def imprecise_arg?(type)
|
|
263
|
-
untyped_arg?(type) || (type.is_a?(Type::Union) && type.members.any? { |member| untyped_arg?(member) })
|
|
264
|
-
end
|
|
265
|
-
|
|
266
294
|
# Pass 1.5: for arity-compatible overloads whose every positional param is either a strict
|
|
267
295
|
# nominal OR a well-known core alias (`int` / `string` / `interned` / etc.), check the arg
|
|
268
296
|
# against the alias's STRICT arm. An Integer literal arg matches `int` here but not `string`, so
|
|
269
297
|
# `Array#*(int)` wins over the `Array#*(string) -> String` overload — even though both translate
|
|
270
298
|
# to `Dynamic[Top]` at the param level. Only fires when EVERY positional param has a known
|
|
271
299
|
# alias-or-strict shape; otherwise gradual matching takes over.
|
|
272
|
-
def find_matching_overload_via_aliases(overloads,
|
|
300
|
+
def find_matching_overload_via_aliases(overloads, shared)
|
|
273
301
|
# Issue #521 — an untyped argument "maybe"-accepts EVERY alias's strict arm, so it cannot
|
|
274
302
|
# discriminate between overloads here any more than in the strict pass; without this guard a
|
|
275
303
|
# Dynamic arg pinned `Array#*(string) -> String` purely by declaration order.
|
|
276
|
-
return nil if
|
|
304
|
+
return nil if ImpreciseArgument.any_in?(shared[:arg_types], shared[:keywords_last])
|
|
277
305
|
|
|
278
306
|
overloads.find do |method_type|
|
|
279
|
-
next false unless engages_block_shape?(method_type, block_required)
|
|
307
|
+
next false unless engages_block_shape?(method_type, shared[:block_required])
|
|
280
308
|
|
|
281
309
|
fun = method_type.type
|
|
310
|
+
ok = KeywordArguments.accepted_by?(fun, shared, false) { |*kv| accepts_param?(*kv, shared, false, true) }
|
|
311
|
+
next false unless ok
|
|
312
|
+
|
|
313
|
+
arg_types = KeywordArguments.selection_positional(method_type, shared)
|
|
282
314
|
next false unless arity_compatible?(fun, arg_types.size)
|
|
283
315
|
|
|
284
316
|
params = positional_params_for(fun, arg_types.size)
|
|
@@ -297,51 +329,22 @@ module Rigor
|
|
|
297
329
|
params.all? { |param| yield(param, arg_types[index += 1]) }
|
|
298
330
|
end
|
|
299
331
|
|
|
300
|
-
# Checks the param's RBS type against an arg using alias-strict-arm matching. Optional / Union
|
|
301
|
-
# wrappers are flattened; alias resolution is one level deep (the canonical core aliases all have
|
|
302
|
-
# non-alias strict arms).
|
|
303
|
-
def alias_param_accepts?(rbs_type, arg)
|
|
304
|
-
nominal_names = strict_nominal_names_for(rbs_type)
|
|
305
|
-
return false if nominal_names.nil? || nominal_names.empty?
|
|
306
|
-
|
|
307
|
-
nominal_names.any? do |class_name|
|
|
308
|
-
result = Type::Combinator.nominal_of(class_name).accepts(arg, mode: :gradual)
|
|
309
|
-
result.yes? || result.maybe?
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
# Returns the candidate class names a param's RBS type accepts under alias-resolved strict
|
|
314
|
-
# matching, or nil when the shape cannot be reduced to a closed set of nominals (e.g. an
|
|
315
|
-
# Interface or an unrecognised alias).
|
|
316
|
-
def strict_nominal_names_for(rbs_type)
|
|
317
|
-
case rbs_type
|
|
318
|
-
when RBS::Types::ClassInstance
|
|
319
|
-
[rbs_type.name.to_s.delete_prefix("::")]
|
|
320
|
-
when RBS::Types::Alias
|
|
321
|
-
ALIAS_STRICT_NOMINALS[rbs_type.name.to_s]
|
|
322
|
-
when RBS::Types::Optional
|
|
323
|
-
strict_nominal_names_for(rbs_type.type)
|
|
324
|
-
when RBS::Types::Union
|
|
325
|
-
parts = rbs_type.types.map { |t| strict_nominal_names_for(t) }
|
|
326
|
-
return nil if parts.any?(&:nil?)
|
|
327
|
-
|
|
328
|
-
parts.flatten
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
|
|
332
332
|
# Returns true when every positional param the call site engages translates to a non-`Dynamic[Top]`
|
|
333
333
|
# carrier. Alias / Interface / Intersection RBS types all degrade to `Dynamic[Top]` per the
|
|
334
334
|
# translator's current shape — those gradually accept any arg, so an overload that includes one
|
|
335
335
|
# would beat strictly-typed alternatives in pass 2 of the selector.
|
|
336
|
-
def strictly_typed_params?(method_type,
|
|
336
|
+
def strictly_typed_params?(method_type, shared)
|
|
337
|
+
actual_count = KeywordArguments.selection_positional(method_type, shared).size
|
|
337
338
|
fun = method_type.type
|
|
338
339
|
# A `(?)` method type declares no params at all: it is the gradual case by construction, so it
|
|
339
340
|
# must never win the strict pass over a genuinely typed sibling overload.
|
|
340
341
|
return false unless fun.respond_to?(:required_positionals)
|
|
341
342
|
return false unless arity_compatible?(fun, actual_count)
|
|
342
343
|
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
# #1737 — the keywords the call passes count too, so an `untyped` keyword cannot win the strict pass over
|
|
345
|
+
# a typed one.
|
|
346
|
+
positional_params_for(fun, actual_count).all? { |param| !alias_or_interface_param?(param.type) } &&
|
|
347
|
+
KeywordArguments.passed_params(fun, shared).all? { |param| !alias_or_interface_param?(param.type) }
|
|
345
348
|
end
|
|
346
349
|
|
|
347
350
|
# Recursive: an Optional / Union wrapper is strict iff every member is strict. Type args of a
|
|
@@ -368,26 +371,33 @@ module Rigor
|
|
|
368
371
|
end
|
|
369
372
|
|
|
370
373
|
# `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
|
|
371
|
-
# `strict:` is `false` (gradual), `true` (strict pass)
|
|
374
|
+
# `strict:` is `false` (gradual), `true` (strict pass), `:proven` (pass 0; see `find_proven_overload`) or
|
|
375
|
+
# `:yes` (the strict pass with a `yes` at every pair, #1800's proof; see `KeywordArguments.prefer_takers`).
|
|
372
376
|
def matches?(method_type, shared, strict: false)
|
|
373
|
-
return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
|
|
374
|
-
|
|
375
|
-
arg_types = shared[:arg_types]
|
|
376
377
|
fun = method_type.type
|
|
377
|
-
|
|
378
|
+
ok = KeywordArguments.accepted_by?(fun, shared, strict) { |*kv| accepts_param?(*kv, shared, strict, true) }
|
|
379
|
+
return false unless ok
|
|
378
380
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
+
arg_types = KeywordArguments.selection_positional(method_type, shared)
|
|
382
|
+
splats = shared[:splats]
|
|
383
|
+
return positionals_accept?(fun, arg_types, shared, strict) unless splats
|
|
384
|
+
|
|
385
|
+
# #1801 — a splat stands for any number of arguments: some count of its elements must fit.
|
|
386
|
+
lists = SplatArity.expansions(fun, arg_types, splats)
|
|
387
|
+
return lists.any? { |list| positionals_accept?(fun, list, shared, strict) } if lists
|
|
388
|
+
|
|
389
|
+
# Past the cap the position each other argument lands at is not spelled out, but it lands at one.
|
|
390
|
+
params = KeywordArguments.positional_params(fun)
|
|
391
|
+
arg_types.each_with_index.all? do |arg, index|
|
|
392
|
+
splats.include?(index) || params.any? { |param| accepts_param?(param, arg, shared, strict) }
|
|
393
|
+
end
|
|
381
394
|
end
|
|
382
395
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
# candidate; we skip it instead of forcing a fallback.
|
|
386
|
-
def rejects_keyword_required?(method_type)
|
|
387
|
-
fun = method_type.type
|
|
388
|
-
return false unless fun.respond_to?(:required_keywords)
|
|
396
|
+
def positionals_accept?(fun, arg_types, shared, strict)
|
|
397
|
+
return false unless arity_compatible?(fun, arg_types.size)
|
|
389
398
|
|
|
390
|
-
|
|
399
|
+
params = positional_params_for(fun, arg_types.size)
|
|
400
|
+
each_param_accepts?(params, arg_types) { |kw, arg| accepts_param?(kw, arg, shared, strict) }
|
|
391
401
|
end
|
|
392
402
|
|
|
393
403
|
# `RBS::Types::UntypedFunction` (`(?)`) declares no arity to enforce, so every call site is
|
|
@@ -434,23 +444,25 @@ module Rigor
|
|
|
434
444
|
end
|
|
435
445
|
|
|
436
446
|
# `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
|
|
437
|
-
def accepts_param?(param, arg, shared, strict)
|
|
447
|
+
def accepts_param?(param, arg, shared, strict, keyword = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
438
448
|
param_type = shared[:param_overrides][param.name] || RbsTypeTranslator.translate(
|
|
439
449
|
param.type,
|
|
440
450
|
self_type: shared[:self_type],
|
|
441
451
|
instance_type: shared[:instance_type],
|
|
442
452
|
type_vars: shared[:type_vars],
|
|
443
|
-
alias_expander: shared[:
|
|
453
|
+
alias_expander: shared[:environment]&.rbs_loader
|
|
444
454
|
)
|
|
445
455
|
# An `untyped` arg gradually accepts against every param, so a value-pinning param would be
|
|
446
456
|
# "matched" with zero evidence and its value-precise return (`(nil) -> []`) would beat broader
|
|
447
457
|
# overloads purely by list position. Decline the pair; only the strict pass (where the arg
|
|
448
458
|
# proves the value) or the final first-overload fallback may select such an overload. (Pass 1
|
|
449
459
|
# already skips untyped args entirely, so this only engages pass 2.)
|
|
450
|
-
|
|
460
|
+
# A keyword value is exempt (#1737): an untyped one marks the call imprecise (`ImpreciseArgument.any_in?`),
|
|
461
|
+
# so every arm it reaches joins the #521 union instead of one winning by position.
|
|
462
|
+
return false if !keyword && ImpreciseArgument.untyped?(arg) && value_pinning?(param_type)
|
|
451
463
|
|
|
452
464
|
result = param_type.accepts(arg, mode: :gradual)
|
|
453
|
-
return result
|
|
465
|
+
return proof?(result, param_type, arg, strict) if strict.is_a?(Symbol)
|
|
454
466
|
|
|
455
467
|
# A record's `maybe` for a `Hash` with a gradual arm or an open shape is no evidence for the overload:
|
|
456
468
|
# with `({ a: Integer }) -> Integer | (Hash[Symbol, untyped]) -> String`, `{ **o, b: 2 }` has a key the
|
|
@@ -461,6 +473,11 @@ module Rigor
|
|
|
461
473
|
result.yes? || result.maybe?
|
|
462
474
|
end
|
|
463
475
|
|
|
476
|
+
# Pass 0 (`:proven`) takes a `yes` naming the argument's own class; #1800's keyword proof (`:yes`) any `yes`.
|
|
477
|
+
def proof?(result, param_type, arg, strict)
|
|
478
|
+
result.yes? && (strict == :yes || ProvenOverload.names_arg_class?(param_type, arg))
|
|
479
|
+
end
|
|
480
|
+
|
|
464
481
|
# A type that admits only specific VALUES rather than a class of values: a `Constant` carrier
|
|
465
482
|
# (RBS `nil` and literal types both translate to one) or a union made up entirely of them
|
|
466
483
|
# (`true | false`, `1 | 2`, `nil?`-style optionals of literals).
|