rigortype 0.4.0 → 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 +4 -3
- 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/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -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/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- 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 +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- 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 +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -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 +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- 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 +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- 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 +73 -14
- 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 +305 -54
- 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 +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- 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/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- 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/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- 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 +59 -3
- 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 +13 -2
- 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/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -243,7 +243,8 @@ module Rigor
|
|
|
243
243
|
method_name: context.method_name,
|
|
244
244
|
args: context.args,
|
|
245
245
|
environment: environment,
|
|
246
|
-
scope: context.scope
|
|
246
|
+
scope: context.scope,
|
|
247
|
+
call_node: context.call_node
|
|
247
248
|
)
|
|
248
249
|
end
|
|
249
250
|
|
|
@@ -277,6 +278,11 @@ module Rigor
|
|
|
277
278
|
|
|
278
279
|
def dispatch_one(receiver, method_name, args, environment, block_type, self_type_override = nil, # rubocop:disable Metrics/ParameterLists
|
|
279
280
|
public_only: false, scope: nil, call_node: nil)
|
|
281
|
+
# Issue #1794 — a sequence's inherited Enumerable surface reads its Rigor-private element argument, with
|
|
282
|
+
# `self` still the sequence.
|
|
283
|
+
element = ArithmeticSequenceElements.element_receiver(receiver, method_name, environment)
|
|
284
|
+
self_type_override ||= receiver if element
|
|
285
|
+
receiver = element || receiver
|
|
280
286
|
descriptor = receiver_descriptor(receiver)
|
|
281
287
|
return nil unless descriptor
|
|
282
288
|
|
|
@@ -564,11 +570,11 @@ module Rigor
|
|
|
564
570
|
|
|
565
571
|
memo = core_stdlib_memo(environment, scope)
|
|
566
572
|
key = [class_name.to_s, method_name.to_sym]
|
|
567
|
-
return memo[key] if memo
|
|
573
|
+
return memo[key] if memo.key?(key)
|
|
568
574
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
575
|
+
store_unless_recorded(memo, key) do
|
|
576
|
+
compute_core_stdlib_ancestor_method(environment, class_name, method_name, scope)
|
|
577
|
+
end
|
|
572
578
|
end
|
|
573
579
|
|
|
574
580
|
# Issue #1173 — the include-edge sibling of {#core_stdlib_ancestor_method}: a discovered
|
|
@@ -596,11 +602,11 @@ module Rigor
|
|
|
596
602
|
|
|
597
603
|
memo = mixin_ancestor_memo(environment, scope)
|
|
598
604
|
key = [class_name.to_s, method_name.to_sym]
|
|
599
|
-
return memo[key] if memo
|
|
605
|
+
return memo[key] if memo.key?(key)
|
|
600
606
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
607
|
+
store_unless_recorded(memo, key) do
|
|
608
|
+
compute_included_module_method(environment, class_name, method_name, scope)
|
|
609
|
+
end
|
|
604
610
|
end
|
|
605
611
|
|
|
606
612
|
# The declines are conjunctive and ordered as {compute_core_stdlib_ancestor_method}'s are:
|
|
@@ -626,20 +632,12 @@ module Rigor
|
|
|
626
632
|
end
|
|
627
633
|
|
|
628
634
|
# The same one-slot memo shape as {#core_stdlib_memo}; see there for why it is one slot keyed
|
|
629
|
-
# on the discovery index's identity, and
|
|
635
|
+
# on the discovery index's identity, and what a recording run may store in it.
|
|
630
636
|
MIXIN_ANCESTOR_MEMO_KEY = :__rigor_mixin_ancestor_dispatch__
|
|
631
637
|
private_constant :MIXIN_ANCESTOR_MEMO_KEY
|
|
632
638
|
|
|
633
639
|
def mixin_ancestor_memo(environment, scope)
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
discovery = scope.discovery
|
|
637
|
-
slot = Thread.current[MIXIN_ANCESTOR_MEMO_KEY]
|
|
638
|
-
unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
|
|
639
|
-
slot = [discovery, environment, {}]
|
|
640
|
-
Thread.current[MIXIN_ANCESTOR_MEMO_KEY] = slot
|
|
641
|
-
end
|
|
642
|
-
slot[2]
|
|
640
|
+
ancestor_memo_slot(MIXIN_ANCESTOR_MEMO_KEY, environment, scope)
|
|
643
641
|
end
|
|
644
642
|
|
|
645
643
|
# The declines are conjunctive, so their ORDER is free — and it is chosen so the two that walk
|
|
@@ -800,8 +798,10 @@ module Rigor
|
|
|
800
798
|
# Memo for the whole decision. Every call site of a class asks the same `(class, method)`
|
|
801
799
|
# question, and the answer is a pure function of the frozen discovery index and the
|
|
802
800
|
# environment, so it is cacheable on their identity. A run that is RECORDING ADR-46
|
|
803
|
-
# dependency edges
|
|
804
|
-
#
|
|
801
|
+
# dependency edges stores only the answers whose computation recorded nothing
|
|
802
|
+
# ({#store_unless_recorded}): the shadow probes above read the project's method tables, and
|
|
803
|
+
# serving an answer that read them from the memo would swallow those edges for every later
|
|
804
|
+
# caller.
|
|
805
805
|
#
|
|
806
806
|
# ONE slot, replaced rather than accumulated — see {ExternalAncestorResolution}'s twin for
|
|
807
807
|
# the measurement. A `Scope` hands each analysed file its own discovery index, so an
|
|
@@ -811,15 +811,38 @@ module Rigor
|
|
|
811
811
|
private_constant :CORE_STDLIB_ANCESTOR_MEMO_KEY
|
|
812
812
|
|
|
813
813
|
def core_stdlib_memo(environment, scope)
|
|
814
|
-
|
|
814
|
+
ancestor_memo_slot(CORE_STDLIB_ANCESTOR_MEMO_KEY, environment, scope)
|
|
815
|
+
end
|
|
815
816
|
|
|
817
|
+
# The slot behind both memos. Whether this thread is recording is part of its key, so a slot
|
|
818
|
+
# filled with every answer is never read by a recording caller, which may only reuse the
|
|
819
|
+
# answers {#store_unless_recorded} lets through.
|
|
820
|
+
def ancestor_memo_slot(memo_key, environment, scope)
|
|
816
821
|
discovery = scope.discovery
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
822
|
+
recording = Rigor::Analysis::DependencyRecorder.recording?
|
|
823
|
+
slot = Thread.current[memo_key]
|
|
824
|
+
unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment) && slot[2] == recording
|
|
825
|
+
slot = [discovery, environment, recording, {}]
|
|
826
|
+
Thread.current[memo_key] = slot
|
|
821
827
|
end
|
|
822
|
-
slot[
|
|
828
|
+
slot[3]
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
# Computes an answer, stores it under `key` when it may be served again, and returns it.
|
|
832
|
+
# Outside recording every answer is stored. Under ADR-46 recording the block runs in an
|
|
833
|
+
# ADR-84 WD2 capture window, which sees every edge the block files for the current consumer
|
|
834
|
+
# (the reads it withholds are not among them). An answer whose window stayed empty is stored:
|
|
835
|
+
# computing it again would read the same frozen tables and file nothing again, so serving it
|
|
836
|
+
# from the memo records exactly what recomputing does. That is most of them, since both
|
|
837
|
+
# computations withhold their ancestry walk and return before any recorded probe unless an
|
|
838
|
+
# RBS declaration answered. Any other answer is recomputed on every call, which files its
|
|
839
|
+
# edges each time, as a recording run always did.
|
|
840
|
+
def store_unless_recorded(memo, key, &)
|
|
841
|
+
return memo[key] = yield unless Rigor::Analysis::DependencyRecorder.recording?
|
|
842
|
+
|
|
843
|
+
answer, read_set = Rigor::Analysis::DependencyRecorder.capture(&)
|
|
844
|
+
memo[key] = answer if read_set.reads.empty? && read_set.missing.empty?
|
|
845
|
+
answer
|
|
823
846
|
end
|
|
824
847
|
|
|
825
848
|
# BFS over the scope's as-written ancestry tables — include edges first, then the
|
|
@@ -889,6 +912,7 @@ module Rigor
|
|
|
889
912
|
# The class's own `def self.x` sits ahead of every `extend` — once it has run.
|
|
890
913
|
# `singleton_def_shadows_call?` orders the def against the call site, so a `def self.sig`
|
|
891
914
|
# written AFTER this `sig {}` does not suppress the bridge.
|
|
915
|
+
# ADR-119: shadowing already withholds the bridge, so a possible def withholds too.
|
|
892
916
|
return nil if scope.singleton_def_shadows_call?(current, method_name, call_node)
|
|
893
917
|
|
|
894
918
|
resolved = rbs_complete_extended_module_for(current, extends, environment, scope,
|
|
@@ -927,6 +951,8 @@ module Rigor
|
|
|
927
951
|
nil
|
|
928
952
|
end
|
|
929
953
|
|
|
954
|
+
# ADR-119: "defines" here stops the allow-list walk (a withholding answer), so a possible def stays
|
|
955
|
+
# a definer — treating it as absent would let a later extend bridge a call the def may own.
|
|
930
956
|
def extend_owner_defines?(owner, method_name, call_node, scope, environment)
|
|
931
957
|
return true if scope.instance_def_shadows_call?(owner, method_name, call_node)
|
|
932
958
|
|
|
@@ -986,41 +1012,147 @@ module Rigor
|
|
|
986
1012
|
return override if override
|
|
987
1013
|
|
|
988
1014
|
instance_type = Type::Combinator.nominal_of(class_name)
|
|
989
|
-
resolved_self_type =
|
|
990
|
-
case kind
|
|
991
|
-
when :singleton then Type::Combinator.singleton_of(class_name)
|
|
992
|
-
else instance_type
|
|
993
|
-
end
|
|
994
1015
|
# `self_type_override` lets the user-class fallback path preserve the ORIGINAL receiver as the
|
|
995
1016
|
# substitute for `Bases::Self` — so `Kernel#dup: () -> self` resolved through the Object
|
|
996
1017
|
# fallback returns the caller's type, not Object. `dispatch_one` also routes the receiver's
|
|
997
1018
|
# type-argument-bearing projection through it ({SelfSubstitute}, #1092).
|
|
998
|
-
self_type = self_type_override ||
|
|
1019
|
+
self_type = self_type_override ||
|
|
1020
|
+
(kind == :singleton ? Type::Combinator.singleton_of(class_name) : instance_type)
|
|
1021
|
+
|
|
1022
|
+
keywords_last = keyword_arguments_last?(call_node, args)
|
|
1023
|
+
call_site = [class_name, method_name, kind]
|
|
1024
|
+
if keywords_last && args.last.is_a?(Type::HashShape)
|
|
1025
|
+
distributed = distributed_keyword_return(method_definition, self_type, instance_type, type_vars, args,
|
|
1026
|
+
block_type, scope, call_node, call_site, environment)
|
|
1027
|
+
return distributed unless distributed.equal?(NOT_DISTRIBUTED)
|
|
1028
|
+
end
|
|
999
1029
|
|
|
1000
1030
|
candidates = OverloadSelector.select_candidates(
|
|
1001
1031
|
method_definition,
|
|
1002
1032
|
arg_types: args,
|
|
1033
|
+
keywords_last: keywords_last, splats: splat_indices(call_node, args),
|
|
1003
1034
|
# A `Dynamic` self (#1092) is a return-side answer; overload selection and ReceiverAffinity
|
|
1004
1035
|
# read the static facet, as they did before the substitute carried the wrapping.
|
|
1005
1036
|
self_type: self_type.is_a?(Type::Dynamic) ? self_type.static_facet : self_type,
|
|
1006
|
-
instance_type: instance_type,
|
|
1007
|
-
type_vars: type_vars,
|
|
1037
|
+
instance_type: instance_type, type_vars: type_vars,
|
|
1008
1038
|
block_required: !block_type.nil?,
|
|
1009
1039
|
environment: environment
|
|
1010
1040
|
)
|
|
1011
1041
|
return nil if candidates.empty?
|
|
1012
1042
|
|
|
1013
|
-
call_site = [class_name, method_name, kind]
|
|
1014
1043
|
record_dispatch_provenance(method_definition, candidates.first, scope, call_node, call_site)
|
|
1015
1044
|
join_candidate_returns(
|
|
1016
1045
|
candidates,
|
|
1017
1046
|
method_definition: method_definition,
|
|
1018
1047
|
self_type: self_type, instance_type: instance_type, type_vars: type_vars,
|
|
1019
1048
|
args: args, block_type: block_type, scope: scope, call_node: call_node, call_site: call_site,
|
|
1020
|
-
alias_expander: environment.rbs_loader
|
|
1049
|
+
alias_expander: environment.rbs_loader, keywords_last: keywords_last
|
|
1021
1050
|
)
|
|
1022
1051
|
end
|
|
1023
1052
|
|
|
1053
|
+
# Issue #1746 — a keyword hash holding a union value (`headers: flag` with `flag : bool`) selects per member
|
|
1054
|
+
# at runtime, so the return reads one overload per {KeywordArguments.distributions} list and joins their
|
|
1055
|
+
# returns, as the block probe does for its parameters (#1737). Selecting once for the whole value took one
|
|
1056
|
+
# member's overload (`Symbol` for `(headers: true) -> Symbol | (?headers: false) -> Integer`). Each list
|
|
1057
|
+
# counts only a genuine match: a member no overload takes, or more lists than the limit, answers
|
|
1058
|
+
# `Dynamic[top]` rather than lend the first-overload fallback to the join. A precise union value joins the
|
|
1059
|
+
# returns as a union; a `Dynamic` value keeps the answer `Dynamic`, as the #521 join does, since the split
|
|
1060
|
+
# dropped the value's `nil` and the untyped input behind it. A keyword hash with no union value answers
|
|
1061
|
+
# {NOT_DISTRIBUTED}, and the call selects as before; so does one whose values do not split (#1779) and that no
|
|
1062
|
+
# overload genuinely takes.
|
|
1063
|
+
NOT_DISTRIBUTED = Object.new.freeze
|
|
1064
|
+
private_constant :NOT_DISTRIBUTED
|
|
1065
|
+
|
|
1066
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1067
|
+
def distributed_keyword_return(method_definition, self_type, instance_type, type_vars, args, block_type,
|
|
1068
|
+
scope, call_node, call_site, environment)
|
|
1069
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1070
|
+
# One overload is the only one any member can select, and a wide union value (`receiver_type: Type::t`)
|
|
1071
|
+
# spelled out member by member would only pass the distribution limit.
|
|
1072
|
+
method_types = method_definition.method_types
|
|
1073
|
+
return NOT_DISTRIBUTED if method_types.size < 2
|
|
1074
|
+
# A keyword hash no overload takes as keywords is a positional `Hash` throughout (#1737).
|
|
1075
|
+
return NOT_DISTRIBUTED unless KeywordArguments.any_declares?(method_types)
|
|
1076
|
+
|
|
1077
|
+
keywords = args.last
|
|
1078
|
+
return NOT_DISTRIBUTED if keywords.pairs.none? { |_name, value| KeywordArguments.union_members(value) }
|
|
1079
|
+
|
|
1080
|
+
selector_self = self_type.is_a?(Type::Dynamic) ? self_type.static_facet : self_type
|
|
1081
|
+
per_list = distributed_keyword_matches(method_definition, args, selector_self, instance_type, type_vars,
|
|
1082
|
+
!block_type.nil?, environment, splat_indices(call_node, args))
|
|
1083
|
+
return NOT_DISTRIBUTED if per_list.equal?(NOT_DISTRIBUTED)
|
|
1084
|
+
return Type::Combinator.untyped if per_list.nil?
|
|
1085
|
+
|
|
1086
|
+
overloads = per_list.flat_map(&:last).uniq
|
|
1087
|
+
if overloads.size == 1
|
|
1088
|
+
record_dispatch_provenance(method_definition, overloads.first, scope, call_node, call_site)
|
|
1089
|
+
end
|
|
1090
|
+
returns = per_list.map do |arg_types, matches|
|
|
1091
|
+
join_candidate_returns(
|
|
1092
|
+
matches,
|
|
1093
|
+
method_definition: method_definition,
|
|
1094
|
+
self_type: self_type, instance_type: instance_type, type_vars: type_vars,
|
|
1095
|
+
args: arg_types, block_type: block_type, scope: scope, call_node: call_node, call_site: call_site,
|
|
1096
|
+
alias_expander: environment.rbs_loader, keywords_last: true
|
|
1097
|
+
)
|
|
1098
|
+
end
|
|
1099
|
+
join_distributed_returns(returns, keywords)
|
|
1100
|
+
end
|
|
1101
|
+
|
|
1102
|
+
# Each distribution list with its genuine matches, or nil past the distribution limit or when a list has
|
|
1103
|
+
# none. When no value split (no union value's key discriminates between the overloads, #1779), the one list
|
|
1104
|
+
# is the call as given, and one with no genuine match answers {NOT_DISTRIBUTED}: the call then selects as it
|
|
1105
|
+
# did before #1746, first-overload fallback included (`1.step(10, by: (c ? 1 : 2.0))` is
|
|
1106
|
+
# `Enumerator::ArithmeticSequence` through the incomplete stdlib RBS).
|
|
1107
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1108
|
+
def distributed_keyword_matches(method_definition, args, selector_self, instance_type, type_vars,
|
|
1109
|
+
block_required, environment, splats)
|
|
1110
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1111
|
+
distributions = keyword_distributions(method_definition, args, selector_self, instance_type, type_vars,
|
|
1112
|
+
environment, splats)
|
|
1113
|
+
return nil if distributions.nil?
|
|
1114
|
+
|
|
1115
|
+
distributions.map do |arg_types|
|
|
1116
|
+
matches = keyword_matches(method_definition, arg_types, selector_self, instance_type, type_vars,
|
|
1117
|
+
block_required, environment, splats)
|
|
1118
|
+
return arg_types.equal?(args) ? NOT_DISTRIBUTED : nil if matches.empty?
|
|
1119
|
+
|
|
1120
|
+
[arg_types, matches]
|
|
1121
|
+
end
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
def join_distributed_returns(returns, keywords)
|
|
1125
|
+
return nil if returns.any?(&:nil?)
|
|
1126
|
+
|
|
1127
|
+
joined = Type::Combinator.union(*returns.uniq)
|
|
1128
|
+
dynamic_value = keywords.pairs.any? { |_name, value| value.is_a?(Type::Dynamic) }
|
|
1129
|
+
dynamic_value && !joined.is_a?(Type::Dynamic) ? Type::Combinator.dynamic(joined) : joined
|
|
1130
|
+
end
|
|
1131
|
+
|
|
1132
|
+
# Issue #1727 — whether `args`' last entry is the call's keyword hash (`f(a, k: 1)`), which the overload
|
|
1133
|
+
# selector then matches against an overload's keywords. Only the AST tells a keyword hash from a
|
|
1134
|
+
# braced positional one (`f(a, { k: 1 })`), so a dispatch without the call node, or one whose
|
|
1135
|
+
# arguments are not the node's own (a forwarded `...`, a `send` re-dispatch), answers false and keeps
|
|
1136
|
+
# reading every argument positionally.
|
|
1137
|
+
def keyword_arguments_last?(call_node, args)
|
|
1138
|
+
return false if args.empty? || !call_node.respond_to?(:arguments)
|
|
1139
|
+
|
|
1140
|
+
arguments = call_node.arguments&.arguments
|
|
1141
|
+
!arguments.nil? && arguments.size == args.size && arguments.last.is_a?(Prism::KeywordHashNode)
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
# Issue #1801 — the indices of `args` the call passes as splat arguments (`f(*xs, k: 1)`), whose positional
|
|
1145
|
+
# count is not known statically, or nil without one. As for {#keyword_arguments_last?}, only a call whose
|
|
1146
|
+
# node's arguments are exactly `args` answers.
|
|
1147
|
+
def splat_indices(call_node, args)
|
|
1148
|
+
return nil if args.empty? || !call_node.respond_to?(:arguments)
|
|
1149
|
+
|
|
1150
|
+
arguments = call_node.arguments&.arguments
|
|
1151
|
+
return nil if arguments.nil? || arguments.size != args.size || arguments.none?(Prism::SplatNode)
|
|
1152
|
+
|
|
1153
|
+
arguments.each_index.select { |index| arguments[index].is_a?(Prism::SplatNode) }
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1024
1156
|
# The two provenance side-tables the return-typing tier is the last place able to populate, recorded
|
|
1025
1157
|
# together because they share one gate (`scope` && `call_node`) and one call site.
|
|
1026
1158
|
#
|
|
@@ -1054,10 +1186,16 @@ module Rigor
|
|
|
1054
1186
|
# A candidate whose return does not translate leaves the join incomplete — decline (fail-soft
|
|
1055
1187
|
# to Dynamic downstream) rather than answer a join missing an arm the runtime can take.
|
|
1056
1188
|
# rubocop:disable-next Metrics/ParameterLists
|
|
1189
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1057
1190
|
def join_candidate_returns(candidates, method_definition:, self_type:, instance_type:, type_vars:, args:,
|
|
1058
|
-
block_type:, scope:, call_node:, call_site:, alias_expander: nil
|
|
1191
|
+
block_type:, scope:, call_node:, call_site:, alias_expander: nil,
|
|
1192
|
+
keywords_last: false)
|
|
1193
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1059
1194
|
returns = candidates.map do |method_type|
|
|
1060
|
-
|
|
1195
|
+
# An overload that takes the keyword hash as its keywords binds no positional parameter to it.
|
|
1196
|
+
positional = KeywordArguments.positional(method_type, args, keywords_last)
|
|
1197
|
+
full_type_vars = compose_type_vars(method_type, type_vars, args, positional, block_type, scope,
|
|
1198
|
+
call_node, call_site)
|
|
1061
1199
|
returned = RbsTypeTranslator.translate(
|
|
1062
1200
|
method_type.type.return_type,
|
|
1063
1201
|
self_type: self_type,
|
|
@@ -1067,7 +1205,7 @@ module Rigor
|
|
|
1067
1205
|
)
|
|
1068
1206
|
next returned unless combining_overload?(method_definition, method_type, call_site)
|
|
1069
1207
|
|
|
1070
|
-
class_level_sum(returned, method_type,
|
|
1208
|
+
class_level_sum(returned, method_type, positional)
|
|
1071
1209
|
end
|
|
1072
1210
|
return returns.first if returns.size == 1
|
|
1073
1211
|
return nil if returns.any?(&:nil?)
|
|
@@ -1078,11 +1216,17 @@ module Rigor
|
|
|
1078
1216
|
Type::Combinator.dynamic(Type::Combinator.union(*distinct))
|
|
1079
1217
|
end
|
|
1080
1218
|
|
|
1081
|
-
|
|
1219
|
+
# `positional` is `args` less a keyword hash the overload takes as its keywords (#1727). The block-return
|
|
1220
|
+
# rule reads every argument, the keyword hash included: a keyword parameter naming the block's variable
|
|
1221
|
+
# (`[T] (?default: T) { () -> T } -> T`) is an argument reaching it. The positional binding reads only the
|
|
1222
|
+
# arguments that land in positional parameters.
|
|
1223
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1224
|
+
def compose_type_vars(method_type, type_vars, args, positional, block_type, scope, call_node, call_site)
|
|
1225
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1082
1226
|
vars = compose_block_type_vars(method_type, type_vars, block_type, args,
|
|
1083
1227
|
scope: scope, call_node: call_node, call_site: call_site)
|
|
1084
|
-
compose_arg_type_vars(method_type, vars,
|
|
1085
|
-
|
|
1228
|
+
compose_arg_type_vars(method_type, vars, positional, scope: scope, call_node: call_node,
|
|
1229
|
+
call_site: call_site)
|
|
1086
1230
|
end
|
|
1087
1231
|
|
|
1088
1232
|
# Whether the overload is one `Enumerable` declares for `sum`, whose declared return the tier reads
|
|
@@ -1248,7 +1392,8 @@ module Rigor
|
|
|
1248
1392
|
# Whether an argument the call passes may land in a parameter whose type names `name`, anywhere
|
|
1249
1393
|
# inside it (`hash[_Key, K2]` names `K2`). The argument count is not matched against the
|
|
1250
1394
|
# parameter list: a `*splat` argument stands for any number of arguments, and keyword arguments
|
|
1251
|
-
# arrive as one more entry in `args
|
|
1395
|
+
# arrive as one more entry in `args` (untrimmed, #1727), so any argument counts as reaching every
|
|
1396
|
+
# parameter.
|
|
1252
1397
|
def argument_reaches_variable?(method_type, name, args)
|
|
1253
1398
|
return false if args.empty?
|
|
1254
1399
|
|
|
@@ -1582,11 +1727,15 @@ module Rigor
|
|
|
1582
1727
|
|
|
1583
1728
|
# ----- block parameter probe (Phase C sub-phase 1) -----
|
|
1584
1729
|
|
|
1585
|
-
def probe_block_param_types(receiver:, method_name:, args:, environment:, scope: nil)
|
|
1730
|
+
def probe_block_param_types(receiver:, method_name:, args:, environment:, scope: nil, call_node: nil)
|
|
1586
1731
|
args ||= []
|
|
1732
|
+
keywords_last = keyword_arguments_last?(call_node, args)
|
|
1733
|
+
splat = splat_indices(call_node, args)
|
|
1587
1734
|
case receiver
|
|
1588
|
-
when Type::Union
|
|
1589
|
-
|
|
1735
|
+
when Type::Union
|
|
1736
|
+
probe_block_param_types_union(receiver, method_name, args, environment, scope, keywords_last, splat)
|
|
1737
|
+
else
|
|
1738
|
+
probe_block_param_types_one(receiver, method_name, args, environment, scope, keywords_last, splat)
|
|
1590
1739
|
end
|
|
1591
1740
|
end
|
|
1592
1741
|
|
|
@@ -1594,9 +1743,9 @@ module Rigor
|
|
|
1594
1743
|
# member resolves the same arity and types (otherwise the call sites would have to thread
|
|
1595
1744
|
# per-member binders, which the slice does not support yet). Mismatches degrade to the empty
|
|
1596
1745
|
# array so the binder defaults all params to Dynamic[Top].
|
|
1597
|
-
def probe_block_param_types_union(receiver, method_name, args, environment, scope)
|
|
1746
|
+
def probe_block_param_types_union(receiver, method_name, args, environment, scope, keywords_last, splat)
|
|
1598
1747
|
results = receiver.members.map do |member|
|
|
1599
|
-
probe_block_param_types_one(member, method_name, args, environment, scope)
|
|
1748
|
+
probe_block_param_types_one(member, method_name, args, environment, scope, keywords_last, splat)
|
|
1600
1749
|
end
|
|
1601
1750
|
return [] if results.empty?
|
|
1602
1751
|
return [] unless results.all? { |r| r == results.first }
|
|
@@ -1604,7 +1753,9 @@ module Rigor
|
|
|
1604
1753
|
results.first
|
|
1605
1754
|
end
|
|
1606
1755
|
|
|
1607
|
-
def probe_block_param_types_one(receiver, method_name, args, environment, scope)
|
|
1756
|
+
def probe_block_param_types_one(receiver, method_name, args, environment, scope, keywords_last, splat)
|
|
1757
|
+
receiver = ArithmeticSequenceElements.element_receiver(receiver, method_name, environment, block: true) ||
|
|
1758
|
+
receiver
|
|
1608
1759
|
descriptor = receiver_descriptor(receiver)
|
|
1609
1760
|
return [] unless descriptor
|
|
1610
1761
|
|
|
@@ -1622,7 +1773,8 @@ module Rigor
|
|
|
1622
1773
|
environment: environment,
|
|
1623
1774
|
receiver: receiver,
|
|
1624
1775
|
receiver_args: receiver_args,
|
|
1625
|
-
method_name: method_name
|
|
1776
|
+
method_name: method_name,
|
|
1777
|
+
keywords_last: keywords_last, positional_splat: splat
|
|
1626
1778
|
)
|
|
1627
1779
|
rescue StandardError
|
|
1628
1780
|
[]
|
|
@@ -1631,7 +1783,7 @@ module Rigor
|
|
|
1631
1783
|
# rubocop:disable Metrics/ParameterLists
|
|
1632
1784
|
def extract_block_param_types(method_definition, class_name:, kind:, args:, type_vars:,
|
|
1633
1785
|
environment: nil, receiver: nil, receiver_args: [],
|
|
1634
|
-
method_name: nil)
|
|
1786
|
+
method_name: nil, keywords_last: false, positional_splat: nil)
|
|
1635
1787
|
# rubocop:enable Metrics/ParameterLists
|
|
1636
1788
|
instance_type = Type::Combinator.nominal_of(class_name)
|
|
1637
1789
|
self_type =
|
|
@@ -1652,12 +1804,25 @@ module Rigor
|
|
|
1652
1804
|
substitute = SelfSubstitute.for(receiver, receiver_args, method_name, args, nil)
|
|
1653
1805
|
self_type = Type::Combinator.nominal_of(class_name, type_args: receiver_args) if substitute
|
|
1654
1806
|
|
|
1807
|
+
selector_self = self_type.is_a?(Type::Dynamic) ? self_type.static_facet : self_type
|
|
1808
|
+
if keywords_last
|
|
1809
|
+
return agreed_keyword_block_params(method_definition, args, self_type, selector_self, instance_type,
|
|
1810
|
+
type_vars, environment, positional_splat)
|
|
1811
|
+
end
|
|
1812
|
+
|
|
1813
|
+
# A splat (#1801) leaves the positional count open, so every overload some count reaches must agree.
|
|
1814
|
+
if positional_splat || FacetDistribution.faceted?(args)
|
|
1815
|
+
matches = facet_matches(method_definition, args, selector_self, instance_type, type_vars, true,
|
|
1816
|
+
environment, false, positional_splat)
|
|
1817
|
+
return agreed_block_params(matches, self_type, instance_type, type_vars, environment)
|
|
1818
|
+
end
|
|
1819
|
+
|
|
1655
1820
|
method_type = OverloadSelector.select(
|
|
1656
1821
|
method_definition,
|
|
1657
1822
|
arg_types: args,
|
|
1658
1823
|
# Overload selection reads a `Dynamic` self's static facet, mirroring the return path;
|
|
1659
1824
|
# the substitution verdict here is built from the receiver's type arguments alone.
|
|
1660
|
-
self_type:
|
|
1825
|
+
self_type: selector_self,
|
|
1661
1826
|
instance_type: instance_type,
|
|
1662
1827
|
type_vars: type_vars,
|
|
1663
1828
|
block_required: true,
|
|
@@ -1665,8 +1830,20 @@ module Rigor
|
|
|
1665
1830
|
)
|
|
1666
1831
|
return [] unless method_type
|
|
1667
1832
|
|
|
1833
|
+
block_params_of(method_type, self_type, instance_type, type_vars, environment) || []
|
|
1834
|
+
end
|
|
1835
|
+
|
|
1836
|
+
# The block parameters every overload in `matches` declares alike, or no information (`[]`).
|
|
1837
|
+
def agreed_block_params(matches, self_type, instance_type, type_vars, environment)
|
|
1838
|
+
answers = matches.uniq.map do |method_type|
|
|
1839
|
+
block_params_of(method_type, self_type, instance_type, type_vars, environment)
|
|
1840
|
+
end
|
|
1841
|
+
answers.uniq.size == 1 && answers.first ? answers.first : []
|
|
1842
|
+
end
|
|
1843
|
+
|
|
1844
|
+
def block_params_of(method_type, self_type, instance_type, type_vars, environment)
|
|
1668
1845
|
block = method_type.respond_to?(:block) ? method_type.block : nil
|
|
1669
|
-
return
|
|
1846
|
+
return nil unless block
|
|
1670
1847
|
|
|
1671
1848
|
translate_block_positional_params(
|
|
1672
1849
|
block,
|
|
@@ -1677,6 +1854,80 @@ module Rigor
|
|
|
1677
1854
|
)
|
|
1678
1855
|
end
|
|
1679
1856
|
|
|
1857
|
+
# {KeywordArguments.distributions} for the call's keyword hash. An overload that declares no keywords may read
|
|
1858
|
+
# the hash as a positional `Hash`, and then every union value splits: with
|
|
1859
|
+
# `(a: Integer) -> Integer | (Hash[Symbol, String]) -> String`, `a: Integer | String`'s `String` member takes
|
|
1860
|
+
# the second overload and its `Integer` member the first. A parameter that cannot take a `Hash` (`(String)`)
|
|
1861
|
+
# leaves the split to the keyword declarations.
|
|
1862
|
+
# `splats`: a splat argument hides how many positional arguments precede the hash.
|
|
1863
|
+
def keyword_distributions(method_definition, args, selector_self, instance_type, type_vars, environment,
|
|
1864
|
+
splats)
|
|
1865
|
+
hash = Type::Combinator.nominal_of("Hash")
|
|
1866
|
+
count = splats ? nil : args.size
|
|
1867
|
+
KeywordArguments.distributions(args, true, method_definition.method_types, count) do |param|
|
|
1868
|
+
param_type = RbsTypeTranslator.translate(
|
|
1869
|
+
param.type, self_type: selector_self, instance_type: instance_type, type_vars: type_vars,
|
|
1870
|
+
alias_expander: environment.rbs_loader
|
|
1871
|
+
)
|
|
1872
|
+
!param_type.accepts(hash, mode: :gradual).no?
|
|
1873
|
+
end
|
|
1874
|
+
end
|
|
1875
|
+
|
|
1876
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1877
|
+
def agreed_keyword_block_params(method_definition, args, self_type, selector_self, instance_type, type_vars,
|
|
1878
|
+
environment, splats)
|
|
1879
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1880
|
+
distributions = keyword_distributions(method_definition, args, selector_self, instance_type, type_vars,
|
|
1881
|
+
environment, splats)
|
|
1882
|
+
return [] if distributions.nil?
|
|
1883
|
+
|
|
1884
|
+
candidates = []
|
|
1885
|
+
distributions.each do |arg_types|
|
|
1886
|
+
# Genuine matches only (`member` true): a member no overload takes must not lend the first-overload
|
|
1887
|
+
# fallback to the agreement, which bound `headers: (true | nil)`'s block to the `true` arm.
|
|
1888
|
+
matches = keyword_matches(method_definition, arg_types, selector_self, instance_type, type_vars, true,
|
|
1889
|
+
environment, splats)
|
|
1890
|
+
return [] if matches.empty?
|
|
1891
|
+
|
|
1892
|
+
candidates.concat(matches)
|
|
1893
|
+
end
|
|
1894
|
+
agreed_block_params(candidates, self_type, instance_type, type_vars, environment)
|
|
1895
|
+
end
|
|
1896
|
+
|
|
1897
|
+
# One distribution's genuine matches. A faceted positional argument (a `Dynamic` with sealed facet members) is
|
|
1898
|
+
# read member-wise as `OverloadSelector.select_candidates` reads it, but every list answers only a genuine
|
|
1899
|
+
# match: the first-overload fallback must not count toward the block probe's agreement or the return path's
|
|
1900
|
+
# join.
|
|
1901
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1902
|
+
def keyword_matches(method_definition, arg_types, selector_self, instance_type, type_vars, block_required,
|
|
1903
|
+
environment, splats)
|
|
1904
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1905
|
+
unless FacetDistribution.faceted?(arg_types)
|
|
1906
|
+
return OverloadSelector.select_declared(method_definition, arg_types, selector_self, instance_type,
|
|
1907
|
+
type_vars, block_required, environment, true, true, splats)
|
|
1908
|
+
end
|
|
1909
|
+
|
|
1910
|
+
facet_matches(method_definition, arg_types, selector_self, instance_type, type_vars, block_required,
|
|
1911
|
+
environment, true, splats)
|
|
1912
|
+
end
|
|
1913
|
+
|
|
1914
|
+
# Issue #1750 — the overloads a faceted argument list may reach, read member-wise with genuine matches only.
|
|
1915
|
+
# Where `FacetDistribution.select` falls back to the arguments as given (overloads not provable, more lists
|
|
1916
|
+
# than its cap, or a member no overload takes), the bare `Dynamic` gradually matches every arm and the
|
|
1917
|
+
# strict pass's pick is the first by position, so the answer is every gradual match
|
|
1918
|
+
# (`OverloadSelector.select_declared`'s `:gradual`) and the caller binds only what they all agree on.
|
|
1919
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1920
|
+
def facet_matches(method_definition, arg_types, selector_self, instance_type, type_vars, block_required,
|
|
1921
|
+
environment, keywords_last, splats = nil)
|
|
1922
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1923
|
+
FacetDistribution.select(arg_types, method_definition, member_wise: true, environment:) do |list, member|
|
|
1924
|
+
# The fallback list (`member` false) answers every gradual match rather than the strict pass's pick.
|
|
1925
|
+
OverloadSelector.select_declared(method_definition, list, selector_self, instance_type, type_vars,
|
|
1926
|
+
block_required, environment, member || :gradual, keywords_last,
|
|
1927
|
+
splats)
|
|
1928
|
+
end
|
|
1929
|
+
end
|
|
1930
|
+
|
|
1680
1931
|
# `RBS::Types::Block#type` is normally an `RBS::Types::Function` carrying the block's parameter
|
|
1681
1932
|
# list; some signatures use `RBS::Types::UntypedFunction` (a `(?)` block) which exposes no
|
|
1682
1933
|
# parameter types -- we treat it as "no information" and return an empty array so the binder
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
module MethodDispatcher
|
|
6
|
+
# How `OverloadSelector` reads a call whose positional arguments include a splat (`f(*xs, a: v)`), #1801.
|
|
7
|
+
#
|
|
8
|
+
# A splat stands for any number of arguments, so the call's positional count is not known statically. The
|
|
9
|
+
# selector sees each splat as one untyped entry of the argument list, and counted that way it ruled out every
|
|
10
|
+
# overload whose arity the splat may still meet: `ph(*xs, a: "s")` skipped `(Hash[Symbol, String]) -> String`,
|
|
11
|
+
# which takes `{ a: "s" }` when `xs` is empty (`def ph(h) = h; ph(*[], a: "s")` is `{ a: "s" }`). Instead, an
|
|
12
|
+
# overload matches when some count of the splats' elements fits its arity and every argument, the splats'
|
|
13
|
+
# untyped elements included, is accepted at the parameter that count binds it to.
|
|
14
|
+
module SplatArity
|
|
15
|
+
# The most argument lists {.expansions} spells out; past it, the selector only asks that every other argument
|
|
16
|
+
# fit some positional parameter of the overload.
|
|
17
|
+
LIMIT = 32
|
|
18
|
+
private_constant :LIMIT
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# The argument lists `arg_types` stands for against `fun`, each splat entry (at `splats`, indices into
|
|
23
|
+
# `arg_types`) repeated as often as `fun`'s positional parameters can take, from none upward; nil past
|
|
24
|
+
# {LIMIT}. An untyped `(?)` parameter list takes any count, so the list as given stands for all of them.
|
|
25
|
+
def expansions(fun, arg_types, splats)
|
|
26
|
+
return [arg_types] unless fun.respond_to?(:required_positionals)
|
|
27
|
+
|
|
28
|
+
indices = splats.select { |index| index < arg_types.size }
|
|
29
|
+
return [arg_types] if indices.empty?
|
|
30
|
+
|
|
31
|
+
bound = element_bound(fun, arg_types.size - indices.size)
|
|
32
|
+
counts = counts(indices.size, bound)
|
|
33
|
+
return nil if counts.nil?
|
|
34
|
+
|
|
35
|
+
counts.map { |count| expand(arg_types, indices, count) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The most splat elements a call with `fixed` other positional arguments can pass to `fun`: up to its maximum
|
|
39
|
+
# arity, or, with a `*rest`, one past filling every other parameter, since further elements land in the rest
|
|
40
|
+
# parameter alike.
|
|
41
|
+
def element_bound(fun, fixed)
|
|
42
|
+
declared = fun.required_positionals.size + fun.optional_positionals.size + fun.trailing_positionals.size
|
|
43
|
+
return declared + 1 if fun.rest_positionals
|
|
44
|
+
|
|
45
|
+
[declared - fixed, 0].max
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Every way to give `splats` splats at most `bound` elements in all, or nil past {LIMIT}.
|
|
49
|
+
def counts(splats, bound)
|
|
50
|
+
combinations = [[]]
|
|
51
|
+
splats.times do
|
|
52
|
+
combinations = combinations.flat_map do |partial|
|
|
53
|
+
(0..(bound - partial.sum)).map { |count| partial + [count] }
|
|
54
|
+
end
|
|
55
|
+
return nil if combinations.size > LIMIT
|
|
56
|
+
end
|
|
57
|
+
combinations
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def expand(arg_types, indices, counts)
|
|
61
|
+
expanded = []
|
|
62
|
+
arg_types.each_with_index do |arg, index|
|
|
63
|
+
position = indices.index(index)
|
|
64
|
+
position ? expanded.concat([arg] * counts[position]) : expanded << arg
|
|
65
|
+
end
|
|
66
|
+
expanded
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|