rigortype 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +18 -5
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/02-cli-reference.md +19 -1
- data/docs/manual/03-configuration.md +7 -5
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +41 -4
- 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 +11 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +292 -59
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
- data/lib/rigor/analysis/incremental_session.rb +103 -23
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +241 -37
- data/lib/rigor/analysis/reachability/scan.rb +490 -77
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/run_cache_probe.rb +1 -1
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +62 -13
- data/lib/rigor/analysis/worker_session.rb +8 -5
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +55 -5
- 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 +140 -40
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +6 -3
- data/lib/rigor/cli/unused_command.rb +166 -27
- data/lib/rigor/cli.rb +39 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +61 -2
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +316 -14
- data/lib/rigor/environment/required_features.rb +208 -0
- data/lib/rigor/environment.rb +33 -8
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +43 -3
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +261 -29
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +286 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -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/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +96 -14
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +12 -7
- data/lib/rigor/inference/project_patched_scanner.rb +108 -26
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
- data/lib/rigor/inference/refined_dispatch.rb +320 -0
- data/lib/rigor/inference/repeated_or_writes.rb +2 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +827 -65
- data/lib/rigor/inference/statement_evaluator.rb +294 -51
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +87 -5
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +118 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +433 -0
- data/lib/rigor/lens/members.rb +153 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +280 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +40 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +48 -6
- data/lib/rigor/protection/mutation_cache.rb +3 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +211 -21
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +100 -10
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +30 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +9 -4
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +83 -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
|
|
|
@@ -1006,41 +1012,135 @@ module Rigor
|
|
|
1006
1012
|
return override if override
|
|
1007
1013
|
|
|
1008
1014
|
instance_type = Type::Combinator.nominal_of(class_name)
|
|
1009
|
-
resolved_self_type =
|
|
1010
|
-
case kind
|
|
1011
|
-
when :singleton then Type::Combinator.singleton_of(class_name)
|
|
1012
|
-
else instance_type
|
|
1013
|
-
end
|
|
1014
1015
|
# `self_type_override` lets the user-class fallback path preserve the ORIGINAL receiver as the
|
|
1015
1016
|
# substitute for `Bases::Self` — so `Kernel#dup: () -> self` resolved through the Object
|
|
1016
1017
|
# fallback returns the caller's type, not Object. `dispatch_one` also routes the receiver's
|
|
1017
1018
|
# type-argument-bearing projection through it ({SelfSubstitute}, #1092).
|
|
1018
|
-
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 = ArgumentNodes.keyword_hash_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
|
|
1019
1029
|
|
|
1020
1030
|
candidates = OverloadSelector.select_candidates(
|
|
1021
1031
|
method_definition,
|
|
1022
1032
|
arg_types: args,
|
|
1033
|
+
keywords_last: keywords_last, splats: splat_indices(call_node, args),
|
|
1023
1034
|
# A `Dynamic` self (#1092) is a return-side answer; overload selection and ReceiverAffinity
|
|
1024
1035
|
# read the static facet, as they did before the substitute carried the wrapping.
|
|
1025
1036
|
self_type: self_type.is_a?(Type::Dynamic) ? self_type.static_facet : self_type,
|
|
1026
|
-
instance_type: instance_type,
|
|
1027
|
-
type_vars: type_vars,
|
|
1037
|
+
instance_type: instance_type, type_vars: type_vars,
|
|
1028
1038
|
block_required: !block_type.nil?,
|
|
1029
1039
|
environment: environment
|
|
1030
1040
|
)
|
|
1031
1041
|
return nil if candidates.empty?
|
|
1032
1042
|
|
|
1033
|
-
call_site = [class_name, method_name, kind]
|
|
1034
1043
|
record_dispatch_provenance(method_definition, candidates.first, scope, call_node, call_site)
|
|
1035
1044
|
join_candidate_returns(
|
|
1036
1045
|
candidates,
|
|
1037
1046
|
method_definition: method_definition,
|
|
1038
1047
|
self_type: self_type, instance_type: instance_type, type_vars: type_vars,
|
|
1039
1048
|
args: args, block_type: block_type, scope: scope, call_node: call_node, call_site: call_site,
|
|
1040
|
-
alias_expander: environment.rbs_loader
|
|
1049
|
+
alias_expander: environment.rbs_loader, keywords_last: keywords_last
|
|
1041
1050
|
)
|
|
1042
1051
|
end
|
|
1043
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 #1801 — the indices of `args` the call passes as splat arguments (`f(*xs, k: 1)`), whose positional
|
|
1133
|
+
# count is not known statically, or nil without one. As for {ArgumentNodes.keyword_hash_last?}, only a call
|
|
1134
|
+
# whose node's arguments are exactly `args` answers.
|
|
1135
|
+
def splat_indices(call_node, args)
|
|
1136
|
+
return nil if args.empty? || !call_node.respond_to?(:arguments)
|
|
1137
|
+
|
|
1138
|
+
arguments = call_node.arguments&.arguments
|
|
1139
|
+
return nil if arguments.nil? || arguments.size != args.size || arguments.none?(Prism::SplatNode)
|
|
1140
|
+
|
|
1141
|
+
arguments.each_index.select { |index| arguments[index].is_a?(Prism::SplatNode) }
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1044
1144
|
# The two provenance side-tables the return-typing tier is the last place able to populate, recorded
|
|
1045
1145
|
# together because they share one gate (`scope` && `call_node`) and one call site.
|
|
1046
1146
|
#
|
|
@@ -1074,10 +1174,16 @@ module Rigor
|
|
|
1074
1174
|
# A candidate whose return does not translate leaves the join incomplete — decline (fail-soft
|
|
1075
1175
|
# to Dynamic downstream) rather than answer a join missing an arm the runtime can take.
|
|
1076
1176
|
# rubocop:disable-next Metrics/ParameterLists
|
|
1177
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1077
1178
|
def join_candidate_returns(candidates, method_definition:, self_type:, instance_type:, type_vars:, args:,
|
|
1078
|
-
block_type:, scope:, call_node:, call_site:, alias_expander: nil
|
|
1179
|
+
block_type:, scope:, call_node:, call_site:, alias_expander: nil,
|
|
1180
|
+
keywords_last: false)
|
|
1181
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1079
1182
|
returns = candidates.map do |method_type|
|
|
1080
|
-
|
|
1183
|
+
# An overload that takes the keyword hash as its keywords binds no positional parameter to it.
|
|
1184
|
+
positional = KeywordArguments.positional(method_type, args, keywords_last)
|
|
1185
|
+
full_type_vars = compose_type_vars(method_type, type_vars, args, positional, block_type, scope,
|
|
1186
|
+
call_node, call_site)
|
|
1081
1187
|
returned = RbsTypeTranslator.translate(
|
|
1082
1188
|
method_type.type.return_type,
|
|
1083
1189
|
self_type: self_type,
|
|
@@ -1087,7 +1193,7 @@ module Rigor
|
|
|
1087
1193
|
)
|
|
1088
1194
|
next returned unless combining_overload?(method_definition, method_type, call_site)
|
|
1089
1195
|
|
|
1090
|
-
class_level_sum(returned, method_type,
|
|
1196
|
+
class_level_sum(returned, method_type, positional)
|
|
1091
1197
|
end
|
|
1092
1198
|
return returns.first if returns.size == 1
|
|
1093
1199
|
return nil if returns.any?(&:nil?)
|
|
@@ -1098,11 +1204,17 @@ module Rigor
|
|
|
1098
1204
|
Type::Combinator.dynamic(Type::Combinator.union(*distinct))
|
|
1099
1205
|
end
|
|
1100
1206
|
|
|
1101
|
-
|
|
1207
|
+
# `positional` is `args` less a keyword hash the overload takes as its keywords (#1727). The block-return
|
|
1208
|
+
# rule reads every argument, the keyword hash included: a keyword parameter naming the block's variable
|
|
1209
|
+
# (`[T] (?default: T) { () -> T } -> T`) is an argument reaching it. The positional binding reads only the
|
|
1210
|
+
# arguments that land in positional parameters.
|
|
1211
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1212
|
+
def compose_type_vars(method_type, type_vars, args, positional, block_type, scope, call_node, call_site)
|
|
1213
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1102
1214
|
vars = compose_block_type_vars(method_type, type_vars, block_type, args,
|
|
1103
1215
|
scope: scope, call_node: call_node, call_site: call_site)
|
|
1104
|
-
compose_arg_type_vars(method_type, vars,
|
|
1105
|
-
|
|
1216
|
+
compose_arg_type_vars(method_type, vars, positional, scope: scope, call_node: call_node,
|
|
1217
|
+
call_site: call_site)
|
|
1106
1218
|
end
|
|
1107
1219
|
|
|
1108
1220
|
# Whether the overload is one `Enumerable` declares for `sum`, whose declared return the tier reads
|
|
@@ -1268,7 +1380,8 @@ module Rigor
|
|
|
1268
1380
|
# Whether an argument the call passes may land in a parameter whose type names `name`, anywhere
|
|
1269
1381
|
# inside it (`hash[_Key, K2]` names `K2`). The argument count is not matched against the
|
|
1270
1382
|
# parameter list: a `*splat` argument stands for any number of arguments, and keyword arguments
|
|
1271
|
-
# arrive as one more entry in `args
|
|
1383
|
+
# arrive as one more entry in `args` (untrimmed, #1727), so any argument counts as reaching every
|
|
1384
|
+
# parameter.
|
|
1272
1385
|
def argument_reaches_variable?(method_type, name, args)
|
|
1273
1386
|
return false if args.empty?
|
|
1274
1387
|
|
|
@@ -1602,11 +1715,15 @@ module Rigor
|
|
|
1602
1715
|
|
|
1603
1716
|
# ----- block parameter probe (Phase C sub-phase 1) -----
|
|
1604
1717
|
|
|
1605
|
-
def probe_block_param_types(receiver:, method_name:, args:, environment:, scope: nil)
|
|
1718
|
+
def probe_block_param_types(receiver:, method_name:, args:, environment:, scope: nil, call_node: nil)
|
|
1606
1719
|
args ||= []
|
|
1720
|
+
keywords_last = ArgumentNodes.keyword_hash_last?(call_node, args)
|
|
1721
|
+
splat = splat_indices(call_node, args)
|
|
1607
1722
|
case receiver
|
|
1608
|
-
when Type::Union
|
|
1609
|
-
|
|
1723
|
+
when Type::Union
|
|
1724
|
+
probe_block_param_types_union(receiver, method_name, args, environment, scope, keywords_last, splat)
|
|
1725
|
+
else
|
|
1726
|
+
probe_block_param_types_one(receiver, method_name, args, environment, scope, keywords_last, splat)
|
|
1610
1727
|
end
|
|
1611
1728
|
end
|
|
1612
1729
|
|
|
@@ -1614,9 +1731,9 @@ module Rigor
|
|
|
1614
1731
|
# member resolves the same arity and types (otherwise the call sites would have to thread
|
|
1615
1732
|
# per-member binders, which the slice does not support yet). Mismatches degrade to the empty
|
|
1616
1733
|
# array so the binder defaults all params to Dynamic[Top].
|
|
1617
|
-
def probe_block_param_types_union(receiver, method_name, args, environment, scope)
|
|
1734
|
+
def probe_block_param_types_union(receiver, method_name, args, environment, scope, keywords_last, splat)
|
|
1618
1735
|
results = receiver.members.map do |member|
|
|
1619
|
-
probe_block_param_types_one(member, method_name, args, environment, scope)
|
|
1736
|
+
probe_block_param_types_one(member, method_name, args, environment, scope, keywords_last, splat)
|
|
1620
1737
|
end
|
|
1621
1738
|
return [] if results.empty?
|
|
1622
1739
|
return [] unless results.all? { |r| r == results.first }
|
|
@@ -1624,7 +1741,9 @@ module Rigor
|
|
|
1624
1741
|
results.first
|
|
1625
1742
|
end
|
|
1626
1743
|
|
|
1627
|
-
def probe_block_param_types_one(receiver, method_name, args, environment, scope)
|
|
1744
|
+
def probe_block_param_types_one(receiver, method_name, args, environment, scope, keywords_last, splat)
|
|
1745
|
+
receiver = ArithmeticSequenceElements.element_receiver(receiver, method_name, environment, block: true) ||
|
|
1746
|
+
receiver
|
|
1628
1747
|
descriptor = receiver_descriptor(receiver)
|
|
1629
1748
|
return [] unless descriptor
|
|
1630
1749
|
|
|
@@ -1642,7 +1761,8 @@ module Rigor
|
|
|
1642
1761
|
environment: environment,
|
|
1643
1762
|
receiver: receiver,
|
|
1644
1763
|
receiver_args: receiver_args,
|
|
1645
|
-
method_name: method_name
|
|
1764
|
+
method_name: method_name,
|
|
1765
|
+
keywords_last: keywords_last, positional_splat: splat
|
|
1646
1766
|
)
|
|
1647
1767
|
rescue StandardError
|
|
1648
1768
|
[]
|
|
@@ -1651,7 +1771,7 @@ module Rigor
|
|
|
1651
1771
|
# rubocop:disable Metrics/ParameterLists
|
|
1652
1772
|
def extract_block_param_types(method_definition, class_name:, kind:, args:, type_vars:,
|
|
1653
1773
|
environment: nil, receiver: nil, receiver_args: [],
|
|
1654
|
-
method_name: nil)
|
|
1774
|
+
method_name: nil, keywords_last: false, positional_splat: nil)
|
|
1655
1775
|
# rubocop:enable Metrics/ParameterLists
|
|
1656
1776
|
instance_type = Type::Combinator.nominal_of(class_name)
|
|
1657
1777
|
self_type =
|
|
@@ -1672,12 +1792,25 @@ module Rigor
|
|
|
1672
1792
|
substitute = SelfSubstitute.for(receiver, receiver_args, method_name, args, nil)
|
|
1673
1793
|
self_type = Type::Combinator.nominal_of(class_name, type_args: receiver_args) if substitute
|
|
1674
1794
|
|
|
1795
|
+
selector_self = self_type.is_a?(Type::Dynamic) ? self_type.static_facet : self_type
|
|
1796
|
+
if keywords_last
|
|
1797
|
+
return agreed_keyword_block_params(method_definition, args, self_type, selector_self, instance_type,
|
|
1798
|
+
type_vars, environment, positional_splat)
|
|
1799
|
+
end
|
|
1800
|
+
|
|
1801
|
+
# A splat (#1801) leaves the positional count open, so every overload some count reaches must agree.
|
|
1802
|
+
if positional_splat || FacetDistribution.faceted?(args)
|
|
1803
|
+
matches = facet_matches(method_definition, args, selector_self, instance_type, type_vars, true,
|
|
1804
|
+
environment, false, positional_splat)
|
|
1805
|
+
return agreed_block_params(matches, self_type, instance_type, type_vars, environment)
|
|
1806
|
+
end
|
|
1807
|
+
|
|
1675
1808
|
method_type = OverloadSelector.select(
|
|
1676
1809
|
method_definition,
|
|
1677
1810
|
arg_types: args,
|
|
1678
1811
|
# Overload selection reads a `Dynamic` self's static facet, mirroring the return path;
|
|
1679
1812
|
# the substitution verdict here is built from the receiver's type arguments alone.
|
|
1680
|
-
self_type:
|
|
1813
|
+
self_type: selector_self,
|
|
1681
1814
|
instance_type: instance_type,
|
|
1682
1815
|
type_vars: type_vars,
|
|
1683
1816
|
block_required: true,
|
|
@@ -1685,8 +1818,20 @@ module Rigor
|
|
|
1685
1818
|
)
|
|
1686
1819
|
return [] unless method_type
|
|
1687
1820
|
|
|
1821
|
+
block_params_of(method_type, self_type, instance_type, type_vars, environment) || []
|
|
1822
|
+
end
|
|
1823
|
+
|
|
1824
|
+
# The block parameters every overload in `matches` declares alike, or no information (`[]`).
|
|
1825
|
+
def agreed_block_params(matches, self_type, instance_type, type_vars, environment)
|
|
1826
|
+
answers = matches.uniq.map do |method_type|
|
|
1827
|
+
block_params_of(method_type, self_type, instance_type, type_vars, environment)
|
|
1828
|
+
end
|
|
1829
|
+
answers.uniq.size == 1 && answers.first ? answers.first : []
|
|
1830
|
+
end
|
|
1831
|
+
|
|
1832
|
+
def block_params_of(method_type, self_type, instance_type, type_vars, environment)
|
|
1688
1833
|
block = method_type.respond_to?(:block) ? method_type.block : nil
|
|
1689
|
-
return
|
|
1834
|
+
return nil unless block
|
|
1690
1835
|
|
|
1691
1836
|
translate_block_positional_params(
|
|
1692
1837
|
block,
|
|
@@ -1697,6 +1842,80 @@ module Rigor
|
|
|
1697
1842
|
)
|
|
1698
1843
|
end
|
|
1699
1844
|
|
|
1845
|
+
# {KeywordArguments.distributions} for the call's keyword hash. An overload that declares no keywords may read
|
|
1846
|
+
# the hash as a positional `Hash`, and then every union value splits: with
|
|
1847
|
+
# `(a: Integer) -> Integer | (Hash[Symbol, String]) -> String`, `a: Integer | String`'s `String` member takes
|
|
1848
|
+
# the second overload and its `Integer` member the first. A parameter that cannot take a `Hash` (`(String)`)
|
|
1849
|
+
# leaves the split to the keyword declarations.
|
|
1850
|
+
# `splats`: a splat argument hides how many positional arguments precede the hash.
|
|
1851
|
+
def keyword_distributions(method_definition, args, selector_self, instance_type, type_vars, environment,
|
|
1852
|
+
splats)
|
|
1853
|
+
hash = Type::Combinator.nominal_of("Hash")
|
|
1854
|
+
count = splats ? nil : args.size
|
|
1855
|
+
KeywordArguments.distributions(args, true, method_definition.method_types, count) do |param|
|
|
1856
|
+
param_type = RbsTypeTranslator.translate(
|
|
1857
|
+
param.type, self_type: selector_self, instance_type: instance_type, type_vars: type_vars,
|
|
1858
|
+
alias_expander: environment.rbs_loader
|
|
1859
|
+
)
|
|
1860
|
+
!param_type.accepts(hash, mode: :gradual).no?
|
|
1861
|
+
end
|
|
1862
|
+
end
|
|
1863
|
+
|
|
1864
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1865
|
+
def agreed_keyword_block_params(method_definition, args, self_type, selector_self, instance_type, type_vars,
|
|
1866
|
+
environment, splats)
|
|
1867
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1868
|
+
distributions = keyword_distributions(method_definition, args, selector_self, instance_type, type_vars,
|
|
1869
|
+
environment, splats)
|
|
1870
|
+
return [] if distributions.nil?
|
|
1871
|
+
|
|
1872
|
+
candidates = []
|
|
1873
|
+
distributions.each do |arg_types|
|
|
1874
|
+
# Genuine matches only (`member` true): a member no overload takes must not lend the first-overload
|
|
1875
|
+
# fallback to the agreement, which bound `headers: (true | nil)`'s block to the `true` arm.
|
|
1876
|
+
matches = keyword_matches(method_definition, arg_types, selector_self, instance_type, type_vars, true,
|
|
1877
|
+
environment, splats)
|
|
1878
|
+
return [] if matches.empty?
|
|
1879
|
+
|
|
1880
|
+
candidates.concat(matches)
|
|
1881
|
+
end
|
|
1882
|
+
agreed_block_params(candidates, self_type, instance_type, type_vars, environment)
|
|
1883
|
+
end
|
|
1884
|
+
|
|
1885
|
+
# One distribution's genuine matches. A faceted positional argument (a `Dynamic` with sealed facet members) is
|
|
1886
|
+
# read member-wise as `OverloadSelector.select_candidates` reads it, but every list answers only a genuine
|
|
1887
|
+
# match: the first-overload fallback must not count toward the block probe's agreement or the return path's
|
|
1888
|
+
# join.
|
|
1889
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1890
|
+
def keyword_matches(method_definition, arg_types, selector_self, instance_type, type_vars, block_required,
|
|
1891
|
+
environment, splats)
|
|
1892
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1893
|
+
unless FacetDistribution.faceted?(arg_types)
|
|
1894
|
+
return OverloadSelector.select_declared(method_definition, arg_types, selector_self, instance_type,
|
|
1895
|
+
type_vars, block_required, environment, true, true, splats)
|
|
1896
|
+
end
|
|
1897
|
+
|
|
1898
|
+
facet_matches(method_definition, arg_types, selector_self, instance_type, type_vars, block_required,
|
|
1899
|
+
environment, true, splats)
|
|
1900
|
+
end
|
|
1901
|
+
|
|
1902
|
+
# Issue #1750 — the overloads a faceted argument list may reach, read member-wise with genuine matches only.
|
|
1903
|
+
# Where `FacetDistribution.select` falls back to the arguments as given (overloads not provable, more lists
|
|
1904
|
+
# than its cap, or a member no overload takes), the bare `Dynamic` gradually matches every arm and the
|
|
1905
|
+
# strict pass's pick is the first by position, so the answer is every gradual match
|
|
1906
|
+
# (`OverloadSelector.select_declared`'s `:gradual`) and the caller binds only what they all agree on.
|
|
1907
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1908
|
+
def facet_matches(method_definition, arg_types, selector_self, instance_type, type_vars, block_required,
|
|
1909
|
+
environment, keywords_last, splats = nil)
|
|
1910
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1911
|
+
FacetDistribution.select(arg_types, method_definition, member_wise: true, environment:) do |list, member|
|
|
1912
|
+
# The fallback list (`member` false) answers every gradual match rather than the strict pass's pick.
|
|
1913
|
+
OverloadSelector.select_declared(method_definition, list, selector_self, instance_type, type_vars,
|
|
1914
|
+
block_required, environment, member || :gradual, keywords_last,
|
|
1915
|
+
splats)
|
|
1916
|
+
end
|
|
1917
|
+
end
|
|
1918
|
+
|
|
1700
1919
|
# `RBS::Types::Block#type` is normally an `RBS::Types::Function` carrying the block's parameter
|
|
1701
1920
|
# list; some signatures use `RBS::Types::UntypedFunction` (a `(?)` block) which exposes no
|
|
1702
1921
|
# 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
|
|
@@ -29,7 +29,7 @@ module Rigor
|
|
|
29
29
|
# `StructInstance`, the member map built from the call's arguments (positional or keyword per the
|
|
30
30
|
# class's `keyword_init`). A form / arity mismatch degrades to `Dynamic[top]` rather than a wrong
|
|
31
31
|
# member map. `.members` on the class folds.
|
|
32
|
-
# 3. member reads + `[]` / `to_h` / `deconstruct` / `deconstruct_keys` / `members`
|
|
32
|
+
# 3. member reads + `[]` / `to_h` / `deconstruct` / `deconstruct_keys` / `members` on a
|
|
33
33
|
# *fresh* `StructInstance` -> the precise projected type; member *setters* (`s.x = v`) return the
|
|
34
34
|
# assigned value type. Unhandled / stored-receiver calls return nil so the pipeline projects the
|
|
35
35
|
# instance to its nominal through RbsDispatch.
|
|
@@ -38,8 +38,9 @@ module Rigor
|
|
|
38
38
|
module StructFolding
|
|
39
39
|
module_function
|
|
40
40
|
|
|
41
|
-
# The `[]` / `to_h` / `deconstruct` / `members`
|
|
41
|
+
# The `[]` / `to_h` / `deconstruct` / `members` projections
|
|
42
42
|
# and the reader-redefinition guard are shared with {DataFolding}.
|
|
43
|
+
# `Struct` has no `#with` (issue #1335): that projection stays on {DataFolding} only.
|
|
43
44
|
extend MemberShapeProjection
|
|
44
45
|
|
|
45
46
|
# Issue #595 / #525 — the shared materialisation test; see {StructMaterialization}.
|
|
@@ -239,10 +240,6 @@ module Rigor
|
|
|
239
240
|
when :deconstruct then instance_deconstruct(instance)
|
|
240
241
|
when :deconstruct_keys then instance_deconstruct_keys(instance, args)
|
|
241
242
|
when :members then instance_members(instance)
|
|
242
|
-
when :with
|
|
243
|
-
instance_with(instance, args) do |members, class_name|
|
|
244
|
-
Type::Combinator.struct_instance_of(members: members, class_name: class_name)
|
|
245
|
-
end
|
|
246
243
|
end
|
|
247
244
|
end
|
|
248
245
|
|
|
@@ -332,7 +329,7 @@ module Rigor
|
|
|
332
329
|
node = context.call_node
|
|
333
330
|
return false if node.nil?
|
|
334
331
|
|
|
335
|
-
materialization_call?(node.receiver, context.
|
|
332
|
+
materialization_call?(node.receiver, context.scope)
|
|
336
333
|
end
|
|
337
334
|
|
|
338
335
|
# ADR-48 slice 4 — precise mutated-member re-typing. After a `local.member = v` setter on a
|
|
@@ -34,18 +34,20 @@ module Rigor
|
|
|
34
34
|
# Issue #595 / #525 — the ONE materialisation test. Both places that must answer "was this receiver
|
|
35
35
|
# expression's struct newly built?" go through it: this module's direct member-read gate above, and
|
|
36
36
|
# `ExpressionTyper`'s caller-side `:self`-grant arm. They had forked answers once (the grant arm was
|
|
37
|
-
# tightened for #591 while this one still trusted any chained call),
|
|
38
|
-
# composed `x.dup_self.with(indent: 9)
|
|
39
|
-
#
|
|
37
|
+
# tightened for #591 while this one still trusted any chained call), so both consumers must share this
|
|
38
|
+
# one answer. A `.with` call is never a materialisation: the composed `x.dup_self.with(indent: 9)`
|
|
39
|
+
# shape once slipped through as one, and `Struct#with` no longer exists to fold (#1335).
|
|
40
40
|
#
|
|
41
41
|
# @param node — the receiver EXPRESSION.
|
|
42
|
-
# @param
|
|
43
|
-
def materialization_call?(node,
|
|
42
|
+
# @param scope — the scope the factory and ancestor lookups resolve against.
|
|
43
|
+
def materialization_call?(node, scope)
|
|
44
44
|
return false unless node.is_a?(Prism::CallNode)
|
|
45
45
|
|
|
46
46
|
case node.name
|
|
47
47
|
when :new, :[] then struct_class_expression?(node.receiver, scope)
|
|
48
|
-
|
|
48
|
+
# `Struct` has no `#with` (#1335), so a `.with` call is never a materialisation here; `Data#with` is
|
|
49
|
+
# a frozen copy and needs no freshness gate.
|
|
50
|
+
when :with then false
|
|
49
51
|
else fresh_factory_call?(node, scope)
|
|
50
52
|
end
|
|
51
53
|
end
|
|
@@ -127,26 +129,6 @@ module Rigor
|
|
|
127
129
|
found
|
|
128
130
|
end
|
|
129
131
|
|
|
130
|
-
# `.with` copies the receiver into a new instance — unless the struct wrote its own, which is free to
|
|
131
|
-
# return `self` and would reopen the very hole above.
|
|
132
|
-
#
|
|
133
|
-
# An ANONYMOUS carrier cannot have one: a `StructInstance` with no class name came from a blockless
|
|
134
|
-
# `Struct.new(…)` (the block form defers in {fold_struct_new} before any instance exists), and a
|
|
135
|
-
# blockless factory defines nothing but the generated accessors. So it is the NAMED carriers that
|
|
136
|
-
# need the lookup — and a named one with no scope to ask refuses, since unproven is not fresh.
|
|
137
|
-
def hand_written_with?(receiver, scope)
|
|
138
|
-
return true unless receiver.is_a?(Type::StructInstance)
|
|
139
|
-
|
|
140
|
-
class_name = receiver.class_name
|
|
141
|
-
return false if class_name.nil?
|
|
142
|
-
return true if scope.nil?
|
|
143
|
-
|
|
144
|
-
# The ANCESTOR walk, not the own-class table: a `with` contributed by an included module is just as
|
|
145
|
-
# free to return `self`. Both consumers reach the identical answer because there is one lookup here
|
|
146
|
-
# rather than a parameter each of them supplies (#598 review).
|
|
147
|
-
!scope.user_def_through_ancestors(class_name, :with).first.nil?
|
|
148
|
-
end
|
|
149
|
-
|
|
150
132
|
# An expression naming the struct class itself. All three definition forms ADR-48 supports qualify,
|
|
151
133
|
# because each of them really does construct: a constant whose member layout the project side-table
|
|
152
134
|
# recorded (`Point.new(…)`), a local holding a `StructClass` carrier (`c = Struct.new(:x, :y)` then
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# Issue #1357 — `Kernel#Rational(x, 1)` converts its argument. CRuby's `nurat_convert` returns a
|
|
9
|
+
# `Rational` for a `Float` / `Integer` / `Rational` / `String`-free numerator and for a `BigDecimal` (which
|
|
10
|
+
# has `to_r`; NaN and Infinity raise). A `Complex` is unwrapped only when its imaginary part is an exact
|
|
11
|
+
# zero, otherwise `a / 1` comes back, a `Complex`, so a `Complex` numerator answers `Rational | Complex`.
|
|
12
|
+
# The core RBS spells the `1`-denominator form `[T < Numeric] (T value, 1) -> T`, which binds `T` to the
|
|
13
|
+
# argument's own class and read `Rational(f, 1)` as a `Float`. Any other numerator (a user `Numeric`
|
|
14
|
+
# without `to_r`) keeps the RBS answer.
|
|
15
|
+
module UnitDenominatorRational
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
CONVERTING = Set["Float", "Integer", "Rational", "BigDecimal"].freeze
|
|
19
|
+
private_constant :CONVERTING
|
|
20
|
+
|
|
21
|
+
# Answers `Rational` (or `Rational | Complex`), or nil when the call is not the unit-denominator form.
|
|
22
|
+
def answer(method_name, args)
|
|
23
|
+
return nil unless method_name == :Rational && args.size.between?(2, 3)
|
|
24
|
+
return nil if args.size == 3 && !exception_keyword?(args[2])
|
|
25
|
+
|
|
26
|
+
denominator = args[1]
|
|
27
|
+
return nil unless denominator.is_a?(Type::Constant) && denominator.value.eql?(1)
|
|
28
|
+
|
|
29
|
+
classes = numerator_classes(args.first)
|
|
30
|
+
return nil if classes.nil?
|
|
31
|
+
|
|
32
|
+
members = [Type::Combinator.nominal_of("Rational")]
|
|
33
|
+
members << Type::Combinator.nominal_of("Complex") if classes.include?(:complex)
|
|
34
|
+
Type::Combinator.union(*members)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The trailing `exception:` keyword hash: a `HashShape` whose only key is `:exception`. Its value
|
|
38
|
+
# does not change the answer (a Float that cannot convert raises either way).
|
|
39
|
+
def exception_keyword?(type)
|
|
40
|
+
type.is_a?(Type::HashShape) && type.pairs.keys == [:exception]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# `[:converting]`, `[:complex]` or both for a recognised numerator; nil when any member is unknown.
|
|
44
|
+
def numerator_classes(type)
|
|
45
|
+
case type
|
|
46
|
+
when Type::Union then union_classes(type)
|
|
47
|
+
when Type::IntegerRange then [:converting]
|
|
48
|
+
when Type::Refined then type.base.is_a?(Type::Nominal) ? numerator_classes(type.base) : nil
|
|
49
|
+
when Type::Constant then class_kind(type.value.class.name)
|
|
50
|
+
when Type::Nominal then type.type_args.empty? ? class_kind(type.class_name) : nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def union_classes(union)
|
|
55
|
+
kinds = union.members.map { |member| numerator_classes(member) }
|
|
56
|
+
kinds.any?(&:nil?) ? nil : kinds.flatten.uniq
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def class_kind(name)
|
|
60
|
+
return [:complex] if name == "Complex"
|
|
61
|
+
|
|
62
|
+
CONVERTING.include?(name) ? [:converting] : nil
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|