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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../../type"
|
|
4
4
|
require_relative "singleton_folding"
|
|
5
5
|
require_relative "hash_transform_keys_folding"
|
|
6
|
+
require_relative "integer_step_block_params"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Inference
|
|
@@ -23,6 +24,10 @@ module Rigor
|
|
|
23
24
|
# bound from the argument.
|
|
24
25
|
# - `a.downto(b) { |i| … }` yields the same domain `[b, a]`, just iterated in reverse. Lower bound
|
|
25
26
|
# from the argument, upper bound from the receiver.
|
|
27
|
+
# - `a.step(limit, by) { |i| … }` (and the `by:` / `to:` keyword forms) yields `Integer` when the receiver,
|
|
28
|
+
# the limit and the step are all Integer; any other operand keeps the RBS `(Numeric)` binding
|
|
29
|
+
# (`1.step(10, 0.5)` yields Floats). RBS declares every `Numeric#step` block as `(Numeric)`, which reports
|
|
30
|
+
# `even?` on correct Integer loops (issue #1783); see {IntegerStepBlockParams}.
|
|
26
31
|
# - `h.transform_keys(mapping) { |k| … }` yields the receiver's keys, a rule the RBS probe does not state for
|
|
27
32
|
# a union of receiver shapes ({HashTransformKeysFolding.block_param_types}).
|
|
28
33
|
module IteratorDispatch
|
|
@@ -38,6 +43,7 @@ module Rigor
|
|
|
38
43
|
when :times then times_block_params(receiver)
|
|
39
44
|
when :upto then upto_block_params(receiver, args.first)
|
|
40
45
|
when :downto then downto_block_params(receiver, args.first)
|
|
46
|
+
when :step then IntegerStepBlockParams.block_param_types(receiver, args)
|
|
41
47
|
when :each_with_index then each_with_index_block_params(receiver)
|
|
42
48
|
when :each_with_object then each_with_object_block_params(receiver, args.first)
|
|
43
49
|
when :inject, :reduce then inject_block_params(receiver, args)
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# How `OverloadSelector` reads a call's keyword arguments (#1727).
|
|
9
|
+
#
|
|
10
|
+
# A call's arguments reach the selector as one positional list, the keyword hash (`f(a, k: 1)`) typed as its
|
|
11
|
+
# last entry. Only the AST tells it from a braced positional hash (`f(a, { k: 1 })`), so the caller says which
|
|
12
|
+
# it is (`keywords_last`). An overload that declares keywords takes that hash as its keywords; one that
|
|
13
|
+
# declares none reads it as a trailing positional `Hash`, as Ruby passes it.
|
|
14
|
+
module KeywordArguments
|
|
15
|
+
NO_PARAMS = [].freeze
|
|
16
|
+
private_constant :NO_PARAMS
|
|
17
|
+
|
|
18
|
+
# The most argument lists {.distributions} spells out; past it, the block probe answers no information.
|
|
19
|
+
DISTRIBUTION_LIMIT = 8
|
|
20
|
+
private_constant :DISTRIBUTION_LIMIT
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# The actuals `method_type`'s positional parameters read: every argument, less the trailing keyword hash
|
|
25
|
+
# when the call passes one and the overload declares keywords to take it.
|
|
26
|
+
def positional(method_type, arg_types, keywords_last)
|
|
27
|
+
return arg_types unless keywords_last && !arg_types.empty? && declares?(method_type.type)
|
|
28
|
+
|
|
29
|
+
arg_types[0...-1]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# {.positional} for the selector's `shared` bundle, computed once per selection: every overload that takes the
|
|
33
|
+
# keyword hash reads the same trimmed list. A bundle rebuilt with other arguments must reset the cache.
|
|
34
|
+
def selection_positional(method_type, shared)
|
|
35
|
+
return shared[:arg_types] unless shared[:keywords_last] && declares?(method_type.type)
|
|
36
|
+
|
|
37
|
+
shared[:positional_arguments] ||= positional(method_type, shared[:arg_types], true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The argument lists a call with a keyword hash stands for, one per combination of the members of its
|
|
41
|
+
# union-typed keyword values (`headers: bool` stands for `headers: true` and `headers: false`, and so does a
|
|
42
|
+
# `Dynamic[bool]`, which the #521 join produces), or nil when that is more than {DISTRIBUTION_LIMIT} lists. A
|
|
43
|
+
# precise union value selects per member at runtime, so an answer that holds for the call must hold for every
|
|
44
|
+
# member (#1737). Only a key that discriminates between `method_types` ({.discriminating?}) splits (#1779):
|
|
45
|
+
# every overload reads any other key's members alike, so splitting it only multiplies the lists toward the
|
|
46
|
+
# limit (`(a: Symbol, b: Symbol)` with two three-member values made nine). An overload that declares no
|
|
47
|
+
# keywords but may take the hash as a positional `Hash` ({.positional_hash_reader?}, whose block says whether
|
|
48
|
+
# a positional parameter may accept a `Hash`) reads every value against that parameter, so then every union
|
|
49
|
+
# value splits. `count` is the call's argument count, nil when a splat hides it. Without a keyword hash, or
|
|
50
|
+
# with no discriminating union value, the one list, `arg_types` itself.
|
|
51
|
+
def distributions(arg_types, keywords_last, method_types, count = arg_types.size, &hash_param)
|
|
52
|
+
keywords = keywords_last && arg_types.last
|
|
53
|
+
return [arg_types] unless keywords.is_a?(Type::HashShape)
|
|
54
|
+
|
|
55
|
+
choices = split_choices(keywords, method_types) do
|
|
56
|
+
hash_param && positional_hash_reader?(method_types, count, &hash_param)
|
|
57
|
+
end
|
|
58
|
+
return [arg_types] if choices.nil?
|
|
59
|
+
return nil if choices.reduce(1) { |count, (_, members)| count * members.size } > DISTRIBUTION_LIMIT
|
|
60
|
+
|
|
61
|
+
combinations(choices).map do |pairs|
|
|
62
|
+
shape = Type::HashShape.new(
|
|
63
|
+
pairs, required_keys: keywords.required_keys, optional_keys: keywords.optional_keys,
|
|
64
|
+
read_only_keys: keywords.read_only_keys, extra_keys: keywords.extra_keys
|
|
65
|
+
)
|
|
66
|
+
arg_types[0...-1] + [shape]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Each key of `keywords` with the values its lists take: a discriminating union value's members, any other
|
|
71
|
+
# value whole. Nil when no value splits. The block answers whether every union value splits (read once).
|
|
72
|
+
def split_choices(keywords, method_types)
|
|
73
|
+
split = false
|
|
74
|
+
every = nil
|
|
75
|
+
choices = keywords.pairs.map do |name, value|
|
|
76
|
+
members = union_members(value)
|
|
77
|
+
next [name, [value]] unless members
|
|
78
|
+
|
|
79
|
+
unless discriminating?(name, method_types)
|
|
80
|
+
every = yield ? true : false if every.nil?
|
|
81
|
+
next [name, [value]] unless every
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
split = true
|
|
85
|
+
[name, members]
|
|
86
|
+
end
|
|
87
|
+
choices if split
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Whether the members of `name`'s value may select different overloads among `method_types`: the overloads
|
|
91
|
+
# that take the key (by name or through `**rest`) declare it with different types (`headers: true` in one,
|
|
92
|
+
# `?headers: false` in another). Where they all declare one type, a member it rejects is rejected by every
|
|
93
|
+
# overload, so the split only adds lists: four `flag: bool` keywords alone made sixteen. An overload that
|
|
94
|
+
# declares others but not this key rejects it whatever its value. One that declares no keywords reads the
|
|
95
|
+
# hash positionally, which {.positional_hash_reader?} decides for every key at once.
|
|
96
|
+
def discriminating?(name, method_types)
|
|
97
|
+
first = nil
|
|
98
|
+
method_types.any? do |method_type|
|
|
99
|
+
fun = method_type.type
|
|
100
|
+
next false unless declares?(fun)
|
|
101
|
+
|
|
102
|
+
type = (fun.required_keywords[name] || fun.optional_keywords[name] || fun.rest_keywords)&.type
|
|
103
|
+
next false if type.nil?
|
|
104
|
+
|
|
105
|
+
first ||= type
|
|
106
|
+
type != first
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Whether an overload of `method_types` that declares no keywords takes `count` arguments and may read the last,
|
|
111
|
+
# the keyword hash, as a positional `Hash`: the block answers for the RBS parameter at that position (the
|
|
112
|
+
# `(String)` of `(a: Symbol) | (String)` cannot take one, the `(Hash[Symbol, String])` of
|
|
113
|
+
# `(a: Integer) | (Hash[Symbol, String])` can, and its members then select per value), and an untyped `(?)`
|
|
114
|
+
# parameter list may take anything. A nil `count` (a splat hides how many arguments precede the hash, and
|
|
115
|
+
# `ph(*[], a: v)` passes `{ a: v }` to a one-parameter `(Hash)`) asks about every positional parameter.
|
|
116
|
+
def positional_hash_reader?(method_types, count, &)
|
|
117
|
+
method_types.any? do |method_type|
|
|
118
|
+
fun = method_type.type
|
|
119
|
+
next false if declares?(fun)
|
|
120
|
+
next true unless fun.respond_to?(:required_positionals)
|
|
121
|
+
next positional_params(fun).any?(&) if count.nil?
|
|
122
|
+
|
|
123
|
+
param = positional_param_at(fun, count, count - 1)
|
|
124
|
+
!param.nil? && yield(param)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def positional_params(fun)
|
|
129
|
+
params = fun.required_positionals + fun.optional_positionals + fun.trailing_positionals
|
|
130
|
+
fun.rest_positionals ? params + [fun.rest_positionals] : params
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The positional parameter `fun` binds the argument at `index` to when called with `count` arguments, or nil
|
|
134
|
+
# when the count does not fit its arity.
|
|
135
|
+
def positional_param_at(fun, count, index)
|
|
136
|
+
required = fun.required_positionals
|
|
137
|
+
trailing = fun.trailing_positionals
|
|
138
|
+
optional = fun.optional_positionals
|
|
139
|
+
minimum = required.size + trailing.size
|
|
140
|
+
return nil if count < minimum || (fun.rest_positionals.nil? && count > minimum + optional.size)
|
|
141
|
+
return required[index] if index < required.size
|
|
142
|
+
return trailing[index - (count - trailing.size)] if index >= count - trailing.size
|
|
143
|
+
|
|
144
|
+
optional[index - required.size] || fun.rest_positionals
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# The members a keyword value splits into: a union's, or those of a `Dynamic` whose static facet is a union,
|
|
148
|
+
# less `nil`, which the #521 join usually carries from an arm no call takes (as `FacetDistribution` reads a
|
|
149
|
+
# facet). One member left stands in for the value (`Dynamic[false | nil]` reads as `false`), since the bare
|
|
150
|
+
# `Dynamic` would gradually match every overload's keyword. A plain union keeps `nil`: there it is a value
|
|
151
|
+
# the call may pass.
|
|
152
|
+
def union_members(value)
|
|
153
|
+
return value.members if value.is_a?(Type::Union)
|
|
154
|
+
return nil unless value.is_a?(Type::Dynamic) && value.static_facet.is_a?(Type::Union)
|
|
155
|
+
|
|
156
|
+
members = value.static_facet.members.reject { |member| nil_member?(member) }
|
|
157
|
+
members.empty? ? nil : members
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def nil_member?(member)
|
|
161
|
+
return member.value.nil? if member.is_a?(Type::Constant)
|
|
162
|
+
|
|
163
|
+
member.is_a?(Type::Nominal) && member.class_name == "NilClass"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def combinations(choices)
|
|
167
|
+
choices.reduce([{}]) do |partials, (name, members)|
|
|
168
|
+
partials.flat_map { |partial| members.map { |member| partial.merge(name => member) } }
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def declares?(fun)
|
|
173
|
+
return false unless fun.respond_to?(:required_keywords)
|
|
174
|
+
|
|
175
|
+
!fun.required_keywords.empty? || !fun.optional_keywords.empty? || !fun.rest_keywords.nil?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# The keyword hash `fun` takes as its keywords, or nil when the call passes none or `fun` declares none.
|
|
179
|
+
def keyword_hash(fun, arg_types, keywords_last)
|
|
180
|
+
return nil unless keywords_last && !arg_types.empty? && declares?(fun)
|
|
181
|
+
|
|
182
|
+
arg_types.last
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# {.accepted?} for the selector's `shared` bundle: the keyword hash `fun` takes, if any, against its keywords.
|
|
186
|
+
def accepted_by?(fun, shared, strict, &)
|
|
187
|
+
accepted?(fun, keyword_hash(fun, shared[:arg_types], shared[:keywords_last]), strict, &)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def any_declares?(method_types) = method_types.any? { |method_type| declares?(method_type.type) }
|
|
191
|
+
|
|
192
|
+
# Issue #1800 — a keyword hash an overload declares and takes as its keywords is no positional argument of
|
|
193
|
+
# another. Both RBS (the first overload in declared order that takes the call) and Ruby (a method that accepts
|
|
194
|
+
# keywords binds `a: 1` to them) read `(a: Integer) -> Integer | (Object) -> String`'s `ob(a: 1)` as the
|
|
195
|
+
# keyword overload, but `ReceiverAffinity` moves `(Object)` first and the strict pass took it, reading the hash
|
|
196
|
+
# positionally. `matches` is a selection pass's answer for a call with a keyword hash; the block answers the
|
|
197
|
+
# keyword-declaring overloads' proven (`:yes`, every pair accepted with a `yes`) or gradual (`false`) matches.
|
|
198
|
+
# A `maybe` proves nothing: a source-only class is a `maybe` instance of every declared class, so
|
|
199
|
+
# `(a: Integer) | (Object)` would have taken `i(a: Bar.new)` as `Integer`. When one is proven, a
|
|
200
|
+
# positional reader declared after it is dropped. One declared before it is what RBS takes, while a method
|
|
201
|
+
# accepting keywords still binds them as keywords, so both stay and the caller joins their returns. A keyword
|
|
202
|
+
# overload that only gradually takes the hash (an unshaped `**opts`, an imprecise value) proves nothing either
|
|
203
|
+
# way, so it joins the positional readers rather than replace them.
|
|
204
|
+
def prefer_takers(declared, matches)
|
|
205
|
+
readers = matches.reject { |method_type| declares?(method_type.type) }
|
|
206
|
+
return matches if readers.empty?
|
|
207
|
+
|
|
208
|
+
takers = declared.select { |method_type| declares?(method_type.type) }
|
|
209
|
+
proven = yield(takers, :yes).first
|
|
210
|
+
unless proven
|
|
211
|
+
joined = matches | yield(takers, false)
|
|
212
|
+
return joined.size == matches.size ? matches : in_declared_order(declared, joined)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
position = declared.index(proven)
|
|
216
|
+
earlier = readers.select { |method_type| declared.index(method_type) < position }
|
|
217
|
+
kept = matches.select { |method_type| declares?(method_type.type) }
|
|
218
|
+
in_declared_order(declared, kept | [proven] | earlier)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def in_declared_order(declared, method_types)
|
|
222
|
+
method_types.sort_by { |method_type| declared.index(method_type) }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Whether `fun`'s keywords take `keywords` (nil for a call without a keyword hash). Without one, an
|
|
226
|
+
# overload that requires a keyword is not viable. With one, a closed shape must carry every required
|
|
227
|
+
# keyword, and every key it carries must be declared (or taken by `**rest`) with a value the block accepts
|
|
228
|
+
# against the declaration. An unshaped keyword hash (`f(**opts)` over a `Hash`) or a shape that may lack a
|
|
229
|
+
# required key is a gradual match only, never a strict one.
|
|
230
|
+
#
|
|
231
|
+
# @yieldparam param — the keyword's `RBS::Types::Function::Param`.
|
|
232
|
+
# @yieldparam value — the type the call passes for it.
|
|
233
|
+
def accepted?(fun, keywords, strict)
|
|
234
|
+
return true unless fun.respond_to?(:required_keywords)
|
|
235
|
+
return fun.required_keywords.empty? if keywords.nil?
|
|
236
|
+
return !strict unless keywords.is_a?(Type::HashShape)
|
|
237
|
+
|
|
238
|
+
required_present?(fun, keywords, strict) &&
|
|
239
|
+
keywords.pairs.all? do |name, value|
|
|
240
|
+
param = fun.required_keywords[name] || fun.optional_keywords[name] || fun.rest_keywords
|
|
241
|
+
!param.nil? && yield(param, value)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# The declarations the keys of the call's keyword hash land in, for the selector's `shared` bundle; empty
|
|
246
|
+
# without a shaped keyword hash `fun` takes (a key nothing declares lands nowhere).
|
|
247
|
+
def passed_params(fun, shared)
|
|
248
|
+
keywords = keyword_hash(fun, shared[:arg_types], shared[:keywords_last])
|
|
249
|
+
return NO_PARAMS unless keywords.is_a?(Type::HashShape)
|
|
250
|
+
|
|
251
|
+
keywords.pairs.each_key.filter_map do |name|
|
|
252
|
+
fun.required_keywords[name] || fun.optional_keywords[name] || fun.rest_keywords
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def required_present?(fun, keywords, strict)
|
|
257
|
+
fun.required_keywords.each_key.all? do |name|
|
|
258
|
+
next true if keywords.pairs.key?(name) && !keywords.optional_key?(name)
|
|
259
|
+
|
|
260
|
+
# Possibly absent: an optional key, or a key an open shape may carry unseen.
|
|
261
|
+
!strict && (keywords.pairs.key?(name) || keywords.open?)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../source/symbol_name"
|
|
3
4
|
require_relative "../../type"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
@@ -94,7 +95,7 @@ module Rigor
|
|
|
94
95
|
|
|
95
96
|
case arg.value
|
|
96
97
|
when Symbol then arg.value
|
|
97
|
-
when String then arg.value
|
|
98
|
+
when String then Source::SymbolName.of(arg.value)
|
|
98
99
|
end
|
|
99
100
|
end
|
|
100
101
|
end
|