rigortype 0.3.6 → 0.3.7
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/date.yml +6 -3
- data/data/builtins/ruby_core/enumerable.yml +1 -0
- data/data/builtins/ruby_core/exception.yml +2 -1
- data/data/builtins/ruby_core/file.yml +28 -14
- data/data/builtins/ruby_core/hash.yml +5 -2
- data/data/builtins/ruby_core/io.yml +33 -16
- data/data/builtins/ruby_core/random.yml +2 -1
- data/data/builtins/ruby_core/re.yml +6 -3
- data/data/builtins/ruby_core/struct.yml +2 -0
- data/data/builtins/ruby_core/time.yml +2 -1
- data/data/gem_overlay/activesupport/core_ext.rbs +409 -11
- data/docs/manual/02-cli-reference.md +16 -7
- data/docs/manual/05-inspecting-types.md +20 -8
- data/docs/manual/07-plugins.md +4 -0
- data/docs/manual/plugins/rigor-actionpack.md +61 -0
- data/docs/manual/plugins/rigor-activerecord.md +74 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
- data/docs/manual/plugins/rigor-sidekiq.md +22 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
- data/lib/rigor/analysis/check_rules.rb +383 -48
- data/lib/rigor/analysis/crash_signature.rb +116 -0
- data/lib/rigor/analysis/dependency_recorder.rb +18 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +28 -0
- data/lib/rigor/analysis/incremental_session.rb +44 -9
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
- data/lib/rigor/analysis/reachability/graph.rb +14 -5
- data/lib/rigor/analysis/reachability/scan.rb +7 -3
- data/lib/rigor/analysis/result.rb +32 -0
- data/lib/rigor/analysis/run_cache_key.rb +41 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
- data/lib/rigor/analysis/runner.rb +147 -10
- data/lib/rigor/analysis/worker_session.rb +10 -1
- data/lib/rigor/bleeding_edge.rb +7 -6
- data/lib/rigor/builtins/hkt_builtins.rb +45 -6
- data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
- data/lib/rigor/builtins/static_return_refinements.rb +20 -1
- data/lib/rigor/cache/descriptor.rb +42 -2
- data/lib/rigor/cache/incremental_snapshot.rb +40 -3
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
- data/lib/rigor/cache/store.rb +10 -1
- data/lib/rigor/cli/coverage_command.rb +23 -18
- data/lib/rigor/cli/coverage_mutation.rb +13 -17
- data/lib/rigor/cli/coverage_scan.rb +47 -6
- data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
- data/lib/rigor/cli/fused_protection_report.rb +23 -2
- data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
- data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
- data/lib/rigor/cli/mutation_protection_report.rb +31 -2
- data/lib/rigor/cli/sig_gen_command.rb +18 -0
- data/lib/rigor/cli/type_of_command.rb +188 -41
- data/lib/rigor/cli/type_of_renderer.rb +72 -3
- data/lib/rigor/cli/type_scan_command.rb +10 -1
- data/lib/rigor/cli.rb +1 -1
- data/lib/rigor/configuration.rb +7 -4
- data/lib/rigor/effects/plugin_facts.rb +1 -1
- data/lib/rigor/effects/scanner.rb +2 -3
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
- data/lib/rigor/environment/lockfile_resolver.rb +15 -5
- data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
- data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
- data/lib/rigor/environment/rbs_loader.rb +525 -46
- data/lib/rigor/environment.rb +38 -15
- data/lib/rigor/inference/acceptance.rb +172 -20
- data/lib/rigor/inference/captured_locals.rb +62 -0
- data/lib/rigor/inference/content_join.rb +347 -0
- data/lib/rigor/inference/def_handle.rb +15 -3
- data/lib/rigor/inference/def_node_resolver.rb +62 -3
- data/lib/rigor/inference/expression_typer.rb +1020 -141
- data/lib/rigor/inference/fork_map.rb +6 -1
- data/lib/rigor/inference/hkt_reducer.rb +12 -1
- data/lib/rigor/inference/hkt_registry.rb +46 -0
- data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
- data/lib/rigor/inference/index_write_widening.rb +48 -0
- data/lib/rigor/inference/indexed_narrowing.rb +14 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
- data/lib/rigor/inference/method_dispatcher.rb +155 -14
- data/lib/rigor/inference/method_parameter_binder.rb +3 -3
- data/lib/rigor/inference/mutation_widening.rb +244 -174
- data/lib/rigor/inference/narrowing.rb +331 -27
- data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
- data/lib/rigor/inference/pre_eval_constants.rb +15 -0
- data/lib/rigor/inference/precision_scanner.rb +5 -2
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +89 -39
- data/lib/rigor/inference/scope_indexer.rb +1493 -140
- data/lib/rigor/inference/statement_evaluator.rb +276 -91
- data/lib/rigor/inference/struct_fold_safety.rb +186 -27
- data/lib/rigor/inference/version_guard.rb +229 -0
- data/lib/rigor/language_server/selection_range_provider.rb +1 -1
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/inflector.rb +14 -5
- data/lib/rigor/plugin/io_boundary.rb +107 -4
- data/lib/rigor/plugin/loader.rb +14 -0
- data/lib/rigor/protection/analysis_guard.rb +60 -0
- data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
- data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
- data/lib/rigor/protection/discovery_seed.rb +1 -1
- data/lib/rigor/protection/measurement_integrity.rb +36 -0
- data/lib/rigor/protection/mutation_scanner.rb +61 -6
- data/lib/rigor/protection/mutator.rb +121 -18
- data/lib/rigor/reflection.rb +182 -30
- data/lib/rigor/scope/discovery_index.rb +39 -1
- data/lib/rigor/scope.rb +385 -4
- data/lib/rigor/sig_gen/classification.rb +12 -1
- data/lib/rigor/sig_gen/generator.rb +254 -5
- data/lib/rigor/sig_gen/renderer.rb +1 -6
- data/lib/rigor/sig_gen/writer.rb +3 -0
- data/lib/rigor/source/constant_path.rb +79 -2
- data/lib/rigor/triage/catalogue.rb +1 -1
- data/lib/rigor/type/combinator.rb +10 -0
- data/lib/rigor/type/maybe.rb +47 -0
- data/lib/rigor/type/refined.rb +1 -2
- data/lib/rigor/type/result.rb +53 -0
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
- data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
- data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
- data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
- data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
- data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
- data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
- data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
- data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
- data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
- data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
- data/sig/rigor/analysis/baseline.rbs +1 -1
- data/sig/rigor/environment.rbs +3 -0
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/io_boundary.rbs +2 -0
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +25 -1
- data/sig/rigor/type.rbs +95 -1
- data/sig/rigor.rbs +16 -0
- metadata +69 -3
|
@@ -42,11 +42,16 @@ module Rigor
|
|
|
42
42
|
#
|
|
43
43
|
# A child ends at {.run_worker}'s `exit!`, which skips `at_exit` AND object finalizers, so a `Tempfile`
|
|
44
44
|
# a child creates is never reclaimed by the mechanism that reclaims the parent's (issue #330 —
|
|
45
|
-
# `Protection::ClosureKillOracle`'s
|
|
45
|
+
# `Protection::ClosureKillOracle`'s mutant file leaked one file per worker per
|
|
46
46
|
# `rigor coverage --protection --mutation` run). Answering with the directory the parent already removes
|
|
47
47
|
# once every child has been collected makes the child's scratch state the parent's problem, which is the
|
|
48
48
|
# only place it can be solved: the names are random, so nothing outside the child could find them.
|
|
49
49
|
#
|
|
50
|
+
# Still load-bearing after issue #572 gave that oracle a block-scoped temp file. The block unwinds on
|
|
51
|
+
# both paths a child can take on its own — a normal return and the `StandardError` {.run_worker}
|
|
52
|
+
# rescues — but a child killed by a signal unwinds nothing at all, and this directory is what reclaims
|
|
53
|
+
# what it left behind.
|
|
54
|
+
#
|
|
50
55
|
# The directory check is the belt to {.run_worker}'s braces: the offer is only worth taking while the
|
|
51
56
|
# parent still has the directory open, and answering `nil` once it is gone degrades to `Dir.tmpdir`
|
|
52
57
|
# instead of raising `Errno::ENOENT` somewhere far from here.
|
|
@@ -85,7 +85,7 @@ module Rigor
|
|
|
85
85
|
Type::Combinator.union(*reduced)
|
|
86
86
|
when HktBody::NominalApp
|
|
87
87
|
reduced_args = node.args.map { |arg| walk(arg, bindings: bindings, state: state) }
|
|
88
|
-
|
|
88
|
+
reduce_nominal_app(node, reduced_args)
|
|
89
89
|
when HktBody::AppRef
|
|
90
90
|
reduced_args = node.args.map { |arg| walk(arg, bindings: bindings, state: state) }
|
|
91
91
|
reduce_app_ref(node.uri, reduced_args, state: state)
|
|
@@ -96,6 +96,17 @@ module Rigor
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
def reduce_nominal_app(node, reduced_args)
|
|
100
|
+
normalized_name = node.class_name.sub(/\A::/, "")
|
|
101
|
+
if %w[Result Dry::Monads::Result].include?(normalized_name) && reduced_args.size == 2
|
|
102
|
+
Type::Combinator.result_of(reduced_args[0], reduced_args[1])
|
|
103
|
+
elsif %w[Maybe Dry::Monads::Maybe].include?(normalized_name) && reduced_args.size == 1
|
|
104
|
+
Type::Combinator.maybe_of(reduced_args[0])
|
|
105
|
+
else
|
|
106
|
+
Type::Combinator.nominal_of(node.class_name, type_args: reduced_args)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
99
110
|
# ADR-20 § D3 conditional reduction. Resolves the test against the current bindings and
|
|
100
111
|
# picks a branch:
|
|
101
112
|
#
|
|
@@ -160,6 +160,8 @@ module Rigor
|
|
|
160
160
|
# typically `untyped` or stdlib classes.
|
|
161
161
|
# @param reporter [#record, nil] same fail-soft reporter contract the other RBS-extended
|
|
162
162
|
# parsers use.
|
|
163
|
+
|
|
164
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
|
|
163
165
|
def self.scan_rbs_loader(rbs_loader, base: EMPTY, name_scope: nil, reporter: nil)
|
|
164
166
|
return base if rbs_loader.nil?
|
|
165
167
|
|
|
@@ -182,6 +184,50 @@ module Rigor
|
|
|
182
184
|
definitions << defn if defn
|
|
183
185
|
end
|
|
184
186
|
|
|
187
|
+
require_relative "hkt_sugar_translator"
|
|
188
|
+
if rbs_loader.respond_to?(:each_type_alias_decl)
|
|
189
|
+
rbs_loader.each_type_alias_decl do |type_name, decl_entry|
|
|
190
|
+
decl = decl_entry.decl
|
|
191
|
+
next if decl.type_params.empty?
|
|
192
|
+
|
|
193
|
+
uri_str = type_name.to_s.sub(/\A::/, "")
|
|
194
|
+
next unless uri_str.include?("::")
|
|
195
|
+
|
|
196
|
+
uri = uri_str.to_sym
|
|
197
|
+
|
|
198
|
+
params = decl.type_params.map(&:name)
|
|
199
|
+
params_set = params.to_set
|
|
200
|
+
|
|
201
|
+
translator = HktSugarTranslator.new(uri: uri, params_set: params_set, name_scope: name_scope)
|
|
202
|
+
body_tree = translator.translate(decl.type)
|
|
203
|
+
|
|
204
|
+
next unless translator.recursive
|
|
205
|
+
|
|
206
|
+
variance = decl.type_params.map do |p|
|
|
207
|
+
case p.variance
|
|
208
|
+
when :covariant then :out
|
|
209
|
+
when :contravariant then :in
|
|
210
|
+
else :inv
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
registrations << Registration.new(
|
|
215
|
+
uri: uri,
|
|
216
|
+
arity: params.size,
|
|
217
|
+
variance: variance,
|
|
218
|
+
bound: Type::Combinator.untyped
|
|
219
|
+
)
|
|
220
|
+
definitions << Definition.new(
|
|
221
|
+
uri: uri,
|
|
222
|
+
params: params,
|
|
223
|
+
body: "",
|
|
224
|
+
body_tree: body_tree,
|
|
225
|
+
source_path: decl.location&.buffer&.name,
|
|
226
|
+
source_line: decl.location&.start_line
|
|
227
|
+
)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
185
231
|
return base if registrations.empty? && definitions.empty?
|
|
186
232
|
|
|
187
233
|
overlay = new(registrations: registrations, definitions: definitions)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
require_relative "hkt_body"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
class HktSugarTranslator
|
|
9
|
+
class << self
|
|
10
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
11
|
+
def translate(rbs_type, uri:, params_set:, name_scope:)
|
|
12
|
+
new(uri: uri, params_set: params_set, name_scope: name_scope).translate(rbs_type)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :recursive
|
|
17
|
+
|
|
18
|
+
def initialize(uri:, params_set:, name_scope:)
|
|
19
|
+
@uri = uri
|
|
20
|
+
@params_set = params_set
|
|
21
|
+
@name_scope = name_scope
|
|
22
|
+
@recursive = false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def translate(type)
|
|
26
|
+
case type
|
|
27
|
+
when RBS::Types::Alias
|
|
28
|
+
if type.name.to_s.sub(/\A::/, "") == @uri.to_s
|
|
29
|
+
@recursive = true
|
|
30
|
+
args = type.args.map { |a| translate(a) }
|
|
31
|
+
HktBody::AppRef.new(uri: @uri, args: args)
|
|
32
|
+
else
|
|
33
|
+
# Re-wrap other aliases as NominalApp or TypeLeaf?
|
|
34
|
+
# Actually, we can just translate them to TypeLeaf if they have no args,
|
|
35
|
+
# but Rigor handles aliases via RbsLoader.
|
|
36
|
+
# If we don't expand them here, they will just be RbsTypeTranslator.translate'd later?
|
|
37
|
+
# Wait, HktBody doesn't have an AliasRef node. HktBody uses NominalApp for things with args,
|
|
38
|
+
# or TypeLeaf for concrete Rigor::Type.
|
|
39
|
+
|
|
40
|
+
# Since Rigor doesn't support generic aliases outside of HKT AppRef,
|
|
41
|
+
# we can just translate to TypeLeaf.
|
|
42
|
+
fallback_to_type_leaf(type)
|
|
43
|
+
end
|
|
44
|
+
when RBS::Types::ClassInstance
|
|
45
|
+
class_name = type.name.to_s.sub(/\A::/, "")
|
|
46
|
+
if type.args.empty?
|
|
47
|
+
HktBody::TypeLeaf.new(type: Rigor::Type::Nominal.new(class_name))
|
|
48
|
+
else
|
|
49
|
+
args = type.args.map { |a| translate(a) }
|
|
50
|
+
HktBody::NominalApp.new(class_name: class_name, args: args)
|
|
51
|
+
end
|
|
52
|
+
when RBS::Types::Variable
|
|
53
|
+
if @params_set.include?(type.name)
|
|
54
|
+
HktBody::Param.new(name: type.name)
|
|
55
|
+
else
|
|
56
|
+
fallback_to_type_leaf(type)
|
|
57
|
+
end
|
|
58
|
+
when RBS::Types::Union
|
|
59
|
+
HktBody::Union.new(arms: type.types.map { |t| translate(t) })
|
|
60
|
+
when RBS::Types::Optional
|
|
61
|
+
HktBody::Union.new(arms: [
|
|
62
|
+
translate(type.type),
|
|
63
|
+
HktBody::TypeLeaf.new(type: Rigor::Type::Constant.new(nil))
|
|
64
|
+
])
|
|
65
|
+
when RBS::Types::Bases::Nil
|
|
66
|
+
HktBody::TypeLeaf.new(type: Rigor::Type::Constant.new(nil))
|
|
67
|
+
when RBS::Types::Bases::Bool
|
|
68
|
+
HktBody::TypeLeaf.new(type: Rigor::Type::Combinator.union(Rigor::Type::Constant.new(true), Rigor::Type::Constant.new(false)))
|
|
69
|
+
when RBS::Types::Bases::Any
|
|
70
|
+
HktBody::TypeLeaf.new(type: Rigor::Type::Combinator.untyped)
|
|
71
|
+
else
|
|
72
|
+
fallback_to_type_leaf(type)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def fallback_to_type_leaf(type)
|
|
80
|
+
require_relative "rbs_type_translator" unless defined?(RbsTypeTranslator)
|
|
81
|
+
rigor_type = RbsTypeTranslator.translate(
|
|
82
|
+
type,
|
|
83
|
+
alias_expander: nil,
|
|
84
|
+
type_vars: {},
|
|
85
|
+
name_scope: @name_scope,
|
|
86
|
+
self_type: Rigor::Type::Combinator.untyped,
|
|
87
|
+
instance_type: Rigor::Type::Combinator.untyped
|
|
88
|
+
)
|
|
89
|
+
HktBody::TypeLeaf.new(type: rigor_type)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "mutation_widening"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
# `h[k] ||= v`, `h[k] &&= v` and `h[k] += v` store through `[]=`, but Prism gives each its own node class rather
|
|
10
|
+
# than a `[]=` `CallNode`. None of them therefore reached {MutationWidening.widen_after_call}, and a literal-shape
|
|
11
|
+
# binding outlived its justification: an `@h = {}` written only through `@h[k] ||= {}` kept its empty `HashShape`,
|
|
12
|
+
# so `@h.empty?` folded to `Constant[true]` and `return nil if x.nil? || @h.empty?` drew a false
|
|
13
|
+
# `flow.always-truthy-condition` on a hash the class fills.
|
|
14
|
+
#
|
|
15
|
+
# Two consumers need the same recognition and neither had it: {StatementEvaluator} for the straight-line write, and
|
|
16
|
+
# {ScopeIndexer}'s class-ivar pre-pass for the cross-method one (an ivar written in `add` and read in `probe`).
|
|
17
|
+
# `||=` / `&&=` are conditional at runtime, but a widening may only LOSE precision, so answering on the branch that
|
|
18
|
+
# does not store is safe.
|
|
19
|
+
#
|
|
20
|
+
# `Prism::IndexTargetNode` is deliberately absent: it is a multi-assign TARGET, and the `MultiWriteNode` that owns
|
|
21
|
+
# it is where that write is observed.
|
|
22
|
+
module IndexWriteWidening
|
|
23
|
+
NODE_CLASSES = [Prism::IndexOrWriteNode, Prism::IndexAndWriteNode, Prism::IndexOperatorWriteNode].freeze
|
|
24
|
+
|
|
25
|
+
# The method these forms store through — the name the mutator tables are keyed on.
|
|
26
|
+
MUTATOR = :[]=
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
# @param node [Prism::Node]
|
|
31
|
+
def index_write?(node)
|
|
32
|
+
NODE_CLASSES.any? { |klass| node.is_a?(klass) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# `arg_types` is the `[key, stored_value]` pair the caller typed, shaped like a `[]=` call's argument list so
|
|
36
|
+
# the widening joins the stored value into the carrier's content evidence exactly as a real `[]=` does
|
|
37
|
+
# (issue #560). Empty means "no evidence" and widens without joining.
|
|
38
|
+
#
|
|
39
|
+
# @param node [Prism::Node] one of {NODE_CLASSES}
|
|
40
|
+
# @param current_scope [Rigor::Scope]
|
|
41
|
+
# @param arg_types [Array<Rigor::Type::Base>]
|
|
42
|
+
# @return [Rigor::Scope]
|
|
43
|
+
def widen(node:, current_scope:, arg_types: MutationWidening::NO_ARG_TYPES)
|
|
44
|
+
MutationWidening.widen_receiver_aliases(node.receiver, MUTATOR, current_scope, arg_types: arg_types)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -83,6 +83,20 @@ module Rigor
|
|
|
83
83
|
[receiver.first, receiver.last, key]
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
# Issue #544 — whether the RECEIVER's type is precise enough for a recorded slot value to be a
|
|
87
|
+
# fact. A `Dynamic` / `Top` constituent means the collection can hold a caller-supplied value at
|
|
88
|
+
# the slot that `||=` keeps, so recording the default alone would invent a fact (mail's
|
|
89
|
+
# `options[:count] ||= :all` folded a reachable `count: 1` path away). Unions walk their members;
|
|
90
|
+
# everything else — the tracked `HashShape` / `Tuple` carriers this feature was built for, and
|
|
91
|
+
# plain nominals whose RBS read already answers honestly — passes.
|
|
92
|
+
def fully_tracked_receiver_type?(type)
|
|
93
|
+
case type
|
|
94
|
+
when Type::Dynamic, Type::Top then false
|
|
95
|
+
when Type::Union then type.members.all? { |m| fully_tracked_receiver_type?(m) }
|
|
96
|
+
else true
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
86
100
|
# Looks up a recorded narrowing for `receiver[key]` against `scope`, returning the narrowed
|
|
87
101
|
# type or nil when no entry applies. Used by ExpressionTyper's `[]` dispatch to refine the
|
|
88
102
|
# result of a stable indexed read.
|
|
@@ -136,7 +136,7 @@ module Rigor
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
# @return [:always_true, :always_false, :bool, nil]
|
|
139
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
139
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
140
140
|
def predicate_decision(method_name, truthiness, emptiness)
|
|
141
141
|
case method_name
|
|
142
142
|
when :all?
|
|
@@ -159,7 +159,6 @@ module Rigor
|
|
|
159
159
|
:bool
|
|
160
160
|
end
|
|
161
161
|
end
|
|
162
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
163
162
|
|
|
164
163
|
def bool_union
|
|
165
164
|
Type::Combinator.union(
|
|
@@ -185,10 +184,13 @@ module Rigor
|
|
|
185
184
|
end
|
|
186
185
|
|
|
187
186
|
def constant_emptiness(value)
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
187
|
+
# `Range` constants and folded `Set` values (SetFolding wraps a real frozen ::Set) are the
|
|
188
|
+
# Enumerable receivers that reach these folds: `Type::Constant` rejects Array / Hash literals
|
|
189
|
+
# (they become `Tuple` / `HashShape` carriers), and the remaining scalar constants (Integer /
|
|
190
|
+
# Float / Symbol / String / …) are not Enumerable receivers for the filter or predicate methods
|
|
191
|
+
# folded here.
|
|
191
192
|
return range_emptiness(value) if value.is_a?(Range)
|
|
193
|
+
return (value.empty? ? :empty : :non_empty) if value.is_a?(::Set)
|
|
192
194
|
|
|
193
195
|
:unknown
|
|
194
196
|
end
|
|
@@ -220,6 +220,16 @@ module Rigor
|
|
|
220
220
|
method_name = context.method_name
|
|
221
221
|
args = context.args
|
|
222
222
|
|
|
223
|
+
# Issue #539 — every fold in this tier executes the BLOCKLESS form of the method against the
|
|
224
|
+
# constant value. A call site that passes a block (literal or `&arg`) has different semantics —
|
|
225
|
+
# `SET.any? { |n| lookup(n) }` folded to the blockless `Set#any?`'s `true` on any non-empty set,
|
|
226
|
+
# a wrong type the flow rules then amplified (`flow.always-truthy-condition` on our own lib).
|
|
227
|
+
# The catalog's `block_dependent` classification covers the C body's own iterator, not the call
|
|
228
|
+
# site's block, so the guard belongs here. Internal per-element dispatches hand a non-CallNode
|
|
229
|
+
# (e.g. the `&:sym` BlockArgumentNode) as `call_node` — those carry no call-site block.
|
|
230
|
+
call_node = context.call_node
|
|
231
|
+
return nil if call_node.is_a?(Prism::CallNode) && call_node.block
|
|
232
|
+
|
|
223
233
|
if REFLECTIVE_SEND_METHODS.include?(method_name)
|
|
224
234
|
first_arg = args.first
|
|
225
235
|
return nil unless first_arg.is_a?(Type::Constant) && first_arg.value.is_a?(Symbol)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
require_relative "singleton_folding"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module MethodDispatcher
|
|
9
|
+
# Folds `JSON.generate` / `JSON.pretty_generate` to their precise return type.
|
|
10
|
+
#
|
|
11
|
+
# Both methods always return a `String` or raise (`JSON::GeneratorError`, `JSON::NestingError`, …) —
|
|
12
|
+
# never `nil`, never a non-`String` value. Upstream RBS (`stdlib/json/0/json.rbs`) already declares
|
|
13
|
+
# `generate` as `-> String`, but `pretty_generate` is declared `-> untyped`, so every
|
|
14
|
+
# `JSON.pretty_generate` call widens to `Dynamic[top]` and every downstream method call on the result
|
|
15
|
+
# loses precision with it. This tier closes that gap by construction rather than waiting on an
|
|
16
|
+
# upstream RBS fix, and folds `generate` alongside it for symmetry — a `Nominal[String]` answer here
|
|
17
|
+
# is monotone over both today's `untyped` (`pretty_generate`) and today's already-precise `String`
|
|
18
|
+
# (`generate`).
|
|
19
|
+
#
|
|
20
|
+
# Unlike `CGIFolding` / `ShellwordsFolding`, this tier does NOT evaluate the argument. `JSON.generate`
|
|
21
|
+
# is not a pure function whose *result* can be computed at analysis time in general — the object graph
|
|
22
|
+
# can be arbitrarily large, cyclic, or rely on a user-defined `#to_json` — so only the *return type* is
|
|
23
|
+
# folded here, never the *return value*. A future literal-argument constant fold (mirroring
|
|
24
|
+
# `ConstantFolding`'s treatment of other pure stdlib calls) can layer on top of this tier without
|
|
25
|
+
# changing it.
|
|
26
|
+
#
|
|
27
|
+
# === Supported methods
|
|
28
|
+
#
|
|
29
|
+
# * `generate(obj)` / `generate(obj, opts)` — returns `Nominal[String]`.
|
|
30
|
+
# * `pretty_generate(obj)` / `pretty_generate(obj, opts)` — returns `Nominal[String]`.
|
|
31
|
+
#
|
|
32
|
+
# === Unsupported cases
|
|
33
|
+
#
|
|
34
|
+
# Returns `nil` (deferring to the next dispatcher tier) when:
|
|
35
|
+
# - the receiver is not `Singleton[JSON]`,
|
|
36
|
+
# - the method is not `generate` / `pretty_generate` (e.g. `JSON.parse`, `JSON.dump`),
|
|
37
|
+
# - the call has the wrong arity (not 1 or 2 positional arguments).
|
|
38
|
+
module JSONFolding
|
|
39
|
+
JSON_GENERATE_METHODS = Set[:generate, :pretty_generate].freeze
|
|
40
|
+
private_constant :JSON_GENERATE_METHODS
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
# @return [Rigor::Type, nil] folded result, or nil to defer.
|
|
45
|
+
def try_dispatch(context)
|
|
46
|
+
receiver = context.receiver
|
|
47
|
+
method_name = context.method_name
|
|
48
|
+
args = context.args
|
|
49
|
+
return nil unless SingletonFolding.receiver?(receiver, "JSON")
|
|
50
|
+
return nil unless JSON_GENERATE_METHODS.include?(method_name)
|
|
51
|
+
return nil unless args.size.between?(1, 2)
|
|
52
|
+
|
|
53
|
+
Type::Combinator.nominal_of("String")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -80,10 +80,22 @@ module Rigor
|
|
|
80
80
|
# declaration.
|
|
81
81
|
# @return [RBS::MethodType, nil] the chosen overload, or nil when the definition has no method
|
|
82
82
|
# types at all.
|
|
83
|
-
def select(method_definition,
|
|
84
|
-
|
|
83
|
+
def select(method_definition, **) = select_candidates(method_definition, **).first
|
|
84
|
+
|
|
85
|
+
# Issue #521 — like {.select}, but when a `Dynamic[Top]` argument reaches the gradual pass it
|
|
86
|
+
# returns EVERY gradually-matching overload instead of pinning the first. An untyped argument
|
|
87
|
+
# accepts every param indiscriminately, so "first gradual match" is decided by overload-list
|
|
88
|
+
# position, not by types — `[true] * n` with an untyped `n` pinned `Array#*(string) -> String`
|
|
89
|
+
# and answered a wrong precise type the runtime can contradict. The caller unions the candidates'
|
|
90
|
+
# returns, which contains the truth whichever overload the runtime takes. Every other path (strict,
|
|
91
|
+
# alias-resolved, typed-gradual, arity fallback) still yields exactly one candidate, so `select`
|
|
92
|
+
# keeps its historical single answer.
|
|
93
|
+
#
|
|
94
|
+
# @return [Array<RBS::MethodType>] matching overloads; empty when the definition declares none.
|
|
95
|
+
def select_candidates(method_definition, arg_types:, self_type:, instance_type:, type_vars: {},
|
|
96
|
+
block_required: false, environment: nil)
|
|
85
97
|
overloads = method_definition.method_types
|
|
86
|
-
return
|
|
98
|
+
return [] if overloads.empty?
|
|
87
99
|
|
|
88
100
|
# `rigor:v1:param: <name> <refinement>` annotations on this method override the RBS-declared
|
|
89
101
|
# parameter type at the matching name. The map is consumed inside `accepts_param?` so overload
|
|
@@ -96,15 +108,18 @@ module Rigor
|
|
|
96
108
|
# an arbitrary sibling-class arm that only wins by overload-list position.
|
|
97
109
|
overloads = ReceiverAffinity.reorder(overloads, self_type: self_type, environment: environment)
|
|
98
110
|
|
|
111
|
+
alias_expander = environment&.rbs_loader
|
|
99
112
|
passes = lambda do |require_block|
|
|
100
113
|
run_selection_passes(
|
|
101
|
-
overloads,
|
|
102
|
-
|
|
114
|
+
overloads,
|
|
115
|
+
{ arg_types: arg_types, self_type: self_type, instance_type: instance_type,
|
|
116
|
+
type_vars: type_vars, block_required: require_block, param_overrides: param_overrides,
|
|
117
|
+
alias_expander: alias_expander }
|
|
103
118
|
)
|
|
104
119
|
end
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
return
|
|
121
|
+
matches = passes.call(block_required)
|
|
122
|
+
return matches unless matches.empty?
|
|
108
123
|
|
|
109
124
|
# A block at the call site that no block-declaring overload matched: Ruby ignores a block handed
|
|
110
125
|
# to a method that never yields it, so retry treating the block as ignorable rather than failing
|
|
@@ -112,8 +127,8 @@ module Rigor
|
|
|
112
127
|
# `define_command(:x) do … end` against `def define_command: (Symbol) -> Symbol`) degraded to
|
|
113
128
|
# `Dynamic[Top]` — and on a self-send suppressed the whole method's return type.
|
|
114
129
|
if block_required
|
|
115
|
-
|
|
116
|
-
return
|
|
130
|
+
matches = passes.call(false)
|
|
131
|
+
return matches unless matches.empty?
|
|
117
132
|
end
|
|
118
133
|
|
|
119
134
|
# No (usable) block at the call site: prefer an overload that does not REQUIRE a block over
|
|
@@ -121,7 +136,7 @@ module Rigor
|
|
|
121
136
|
# overload first (`() { ... } -> Array[Elem]`) and the bare-call overload second
|
|
122
137
|
# (`() -> Enumerator[...]`). Without this, a no-block `[1, 2].filter` would adopt the block
|
|
123
138
|
# overload's `Array[Elem]` return when the call actually yields an `Enumerator`.
|
|
124
|
-
overloads.find { |mt| !overload_requires_block?(mt) } || overloads.first
|
|
139
|
+
[overloads.find { |mt| !overload_requires_block?(mt) } || overloads.first]
|
|
125
140
|
end
|
|
126
141
|
|
|
127
142
|
def overload_has_block?(method_type)
|
|
@@ -147,38 +162,58 @@ module Rigor
|
|
|
147
162
|
# `Array#*(int)` wins over the `Array#*(string) -> String` overload for Integer args.
|
|
148
163
|
# - Pass 2 (gradual): the original gradual matcher so overloads that legitimately rely on
|
|
149
164
|
# duck-typed params still resolve when nothing stricter applies.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
# `shared` is the caller-assembled keyword bundle for `find_matching_overload` — hash-shaped
|
|
166
|
+
# because the pass pipeline forwards it twice and RuboCop's parameter-list budget is real.
|
|
167
|
+
def run_selection_passes(overloads, shared)
|
|
168
|
+
strict = find_matching_overload(overloads, **shared, strict: true)
|
|
169
|
+
return strict unless strict.empty?
|
|
170
|
+
|
|
171
|
+
alias_hit = find_matching_overload_via_aliases(
|
|
172
|
+
overloads, arg_types: shared[:arg_types], block_required: shared[:block_required]
|
|
173
|
+
)
|
|
174
|
+
return [alias_hit] if alias_hit
|
|
175
|
+
|
|
176
|
+
# Pass 2, array-valued. With every argument carrying real type information the first gradual
|
|
177
|
+
# match keeps its historical single-winner contract. With a `Dynamic[Top]` argument in play the
|
|
178
|
+
# matches are indistinguishable by types — position alone would pick — so ALL of them come back
|
|
179
|
+
# and the dispatch layer unions their returns (#521).
|
|
180
|
+
matches = find_matching_overload(overloads, **shared, strict: false)
|
|
181
|
+
return matches.first(1) unless shared[:arg_types].any? { |t| untyped_arg?(t) }
|
|
182
|
+
|
|
183
|
+
matches
|
|
159
184
|
end
|
|
160
185
|
|
|
161
|
-
# rubocop:disable Metrics/ParameterLists
|
|
186
|
+
# rubocop:disable-next Metrics/ParameterLists
|
|
162
187
|
def find_matching_overload(overloads, arg_types:, self_type:, instance_type:, type_vars:, block_required:,
|
|
163
|
-
param_overrides:, strict:)
|
|
164
|
-
return
|
|
188
|
+
param_overrides:, strict:, alias_expander: nil)
|
|
189
|
+
return [] if strict && arg_types.any? { |t| untyped_arg?(t) }
|
|
165
190
|
|
|
166
|
-
|
|
167
|
-
next false
|
|
168
|
-
next false if !block_required && OverloadSelector.overload_requires_block?(method_type)
|
|
191
|
+
predicate = lambda do |method_type|
|
|
192
|
+
next false unless engages_block_shape?(method_type, block_required)
|
|
169
193
|
next false if strict && !strictly_typed_params?(method_type, arg_types.size)
|
|
170
194
|
|
|
171
195
|
matches?(
|
|
172
|
-
method_type,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
type_vars: type_vars,
|
|
177
|
-
param_overrides: param_overrides
|
|
196
|
+
method_type, arg_types,
|
|
197
|
+
self_type: self_type, instance_type: instance_type,
|
|
198
|
+
type_vars: type_vars, param_overrides: param_overrides,
|
|
199
|
+
alias_expander: alias_expander
|
|
178
200
|
)
|
|
179
201
|
end
|
|
202
|
+
# Strict keeps its historical first-match short-circuit (a dispatch hot path); the gradual
|
|
203
|
+
# pass needs the full candidate list for the #521 union.
|
|
204
|
+
return [overloads.find(&predicate)].compact if strict
|
|
205
|
+
|
|
206
|
+
overloads.select(&predicate)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Whether the overload's block clause is compatible with the call site's block shape: a
|
|
210
|
+
# block-bearing call engages only block-declaring overloads; a block-less call skips overloads
|
|
211
|
+
# that REQUIRE one.
|
|
212
|
+
def engages_block_shape?(method_type, block_required)
|
|
213
|
+
return OverloadSelector.overload_has_block?(method_type) if block_required
|
|
214
|
+
|
|
215
|
+
!OverloadSelector.overload_requires_block?(method_type)
|
|
180
216
|
end
|
|
181
|
-
# rubocop:enable Metrics/ParameterLists
|
|
182
217
|
|
|
183
218
|
# Treats the literal `untyped` carrier (`Dynamic[Top]`) as too imprecise to drive a strict-pass
|
|
184
219
|
# match. Other `Dynamic`-wrapped types with a concrete static facet carry enough information to
|
|
@@ -194,9 +229,13 @@ module Rigor
|
|
|
194
229
|
# to `Dynamic[Top]` at the param level. Only fires when EVERY positional param has a known
|
|
195
230
|
# alias-or-strict shape; otherwise gradual matching takes over.
|
|
196
231
|
def find_matching_overload_via_aliases(overloads, arg_types:, block_required:)
|
|
232
|
+
# Issue #521 — an untyped argument "maybe"-accepts EVERY alias's strict arm, so it cannot
|
|
233
|
+
# discriminate between overloads here any more than in the strict pass; without this guard a
|
|
234
|
+
# Dynamic arg pinned `Array#*(string) -> String` purely by declaration order.
|
|
235
|
+
return nil if arg_types.any? { |t| untyped_arg?(t) }
|
|
236
|
+
|
|
197
237
|
overloads.find do |method_type|
|
|
198
|
-
next false
|
|
199
|
-
next false if !block_required && OverloadSelector.overload_requires_block?(method_type)
|
|
238
|
+
next false unless engages_block_shape?(method_type, block_required)
|
|
200
239
|
|
|
201
240
|
fun = method_type.type
|
|
202
241
|
next false unless arity_compatible?(fun, arg_types.size)
|
|
@@ -278,7 +317,8 @@ module Rigor
|
|
|
278
317
|
end
|
|
279
318
|
end
|
|
280
319
|
|
|
281
|
-
def matches?(method_type, arg_types, self_type:, instance_type:, type_vars:, param_overrides
|
|
320
|
+
def matches?(method_type, arg_types, self_type:, instance_type:, type_vars:, param_overrides:,
|
|
321
|
+
alias_expander: nil)
|
|
282
322
|
return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
|
|
283
323
|
|
|
284
324
|
fun = method_type.type
|
|
@@ -292,7 +332,8 @@ module Rigor
|
|
|
292
332
|
self_type: self_type,
|
|
293
333
|
instance_type: instance_type,
|
|
294
334
|
type_vars: type_vars,
|
|
295
|
-
param_overrides: param_overrides
|
|
335
|
+
param_overrides: param_overrides,
|
|
336
|
+
alias_expander: alias_expander
|
|
296
337
|
)
|
|
297
338
|
end
|
|
298
339
|
end
|
|
@@ -346,12 +387,14 @@ module Rigor
|
|
|
346
387
|
head
|
|
347
388
|
end
|
|
348
389
|
|
|
349
|
-
def accepts_param?(param, arg, self_type:, instance_type:, type_vars:, param_overrides
|
|
390
|
+
def accepts_param?(param, arg, self_type:, instance_type:, type_vars:, param_overrides:,
|
|
391
|
+
alias_expander: nil)
|
|
350
392
|
param_type = param_overrides[param.name] || RbsTypeTranslator.translate(
|
|
351
393
|
param.type,
|
|
352
394
|
self_type: self_type,
|
|
353
395
|
instance_type: instance_type,
|
|
354
|
-
type_vars: type_vars
|
|
396
|
+
type_vars: type_vars,
|
|
397
|
+
alias_expander: alias_expander
|
|
355
398
|
)
|
|
356
399
|
# An `untyped` arg gradually accepts against every param, so a value-pinning param would be
|
|
357
400
|
# "matched" with zero evidence and its value-precise return (`(nil) -> []`) would beat broader
|