rigortype 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/03-configuration.md +1 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +289 -57
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +3 -1
- data/lib/rigor/analysis/incremental_session.rb +14 -1
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
- data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +34 -3
- data/lib/rigor/analysis/worker_session.rb +7 -4
- data/lib/rigor/cache/incremental_snapshot.rb +22 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +13 -7
- data/lib/rigor/cli.rb +10 -2
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +46 -2
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +283 -14
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment.rb +21 -3
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +215 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +259 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +687 -60
- data/lib/rigor/inference/statement_evaluator.rb +71 -20
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/mutation_cache.rb +1 -1
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +189 -10
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +55 -5
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +27 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- metadata +66 -1
|
@@ -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,147 @@ 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 = 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
|
|
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 #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
|
+
|
|
1044
1156
|
# The two provenance side-tables the return-typing tier is the last place able to populate, recorded
|
|
1045
1157
|
# together because they share one gate (`scope` && `call_node`) and one call site.
|
|
1046
1158
|
#
|
|
@@ -1074,10 +1186,16 @@ module Rigor
|
|
|
1074
1186
|
# A candidate whose return does not translate leaves the join incomplete — decline (fail-soft
|
|
1075
1187
|
# to Dynamic downstream) rather than answer a join missing an arm the runtime can take.
|
|
1076
1188
|
# rubocop:disable-next Metrics/ParameterLists
|
|
1189
|
+
# rubocop:disable Metrics/ParameterLists
|
|
1077
1190
|
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
|
|
1191
|
+
block_type:, scope:, call_node:, call_site:, alias_expander: nil,
|
|
1192
|
+
keywords_last: false)
|
|
1193
|
+
# rubocop:enable Metrics/ParameterLists
|
|
1079
1194
|
returns = candidates.map do |method_type|
|
|
1080
|
-
|
|
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)
|
|
1081
1199
|
returned = RbsTypeTranslator.translate(
|
|
1082
1200
|
method_type.type.return_type,
|
|
1083
1201
|
self_type: self_type,
|
|
@@ -1087,7 +1205,7 @@ module Rigor
|
|
|
1087
1205
|
)
|
|
1088
1206
|
next returned unless combining_overload?(method_definition, method_type, call_site)
|
|
1089
1207
|
|
|
1090
|
-
class_level_sum(returned, method_type,
|
|
1208
|
+
class_level_sum(returned, method_type, positional)
|
|
1091
1209
|
end
|
|
1092
1210
|
return returns.first if returns.size == 1
|
|
1093
1211
|
return nil if returns.any?(&:nil?)
|
|
@@ -1098,11 +1216,17 @@ module Rigor
|
|
|
1098
1216
|
Type::Combinator.dynamic(Type::Combinator.union(*distinct))
|
|
1099
1217
|
end
|
|
1100
1218
|
|
|
1101
|
-
|
|
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
|
|
1102
1226
|
vars = compose_block_type_vars(method_type, type_vars, block_type, args,
|
|
1103
1227
|
scope: scope, call_node: call_node, call_site: call_site)
|
|
1104
|
-
compose_arg_type_vars(method_type, vars,
|
|
1105
|
-
|
|
1228
|
+
compose_arg_type_vars(method_type, vars, positional, scope: scope, call_node: call_node,
|
|
1229
|
+
call_site: call_site)
|
|
1106
1230
|
end
|
|
1107
1231
|
|
|
1108
1232
|
# Whether the overload is one `Enumerable` declares for `sum`, whose declared return the tier reads
|
|
@@ -1268,7 +1392,8 @@ module Rigor
|
|
|
1268
1392
|
# Whether an argument the call passes may land in a parameter whose type names `name`, anywhere
|
|
1269
1393
|
# inside it (`hash[_Key, K2]` names `K2`). The argument count is not matched against the
|
|
1270
1394
|
# parameter list: a `*splat` argument stands for any number of arguments, and keyword arguments
|
|
1271
|
-
# 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.
|
|
1272
1397
|
def argument_reaches_variable?(method_type, name, args)
|
|
1273
1398
|
return false if args.empty?
|
|
1274
1399
|
|
|
@@ -1602,11 +1727,15 @@ module Rigor
|
|
|
1602
1727
|
|
|
1603
1728
|
# ----- block parameter probe (Phase C sub-phase 1) -----
|
|
1604
1729
|
|
|
1605
|
-
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)
|
|
1606
1731
|
args ||= []
|
|
1732
|
+
keywords_last = keyword_arguments_last?(call_node, args)
|
|
1733
|
+
splat = splat_indices(call_node, args)
|
|
1607
1734
|
case receiver
|
|
1608
|
-
when Type::Union
|
|
1609
|
-
|
|
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)
|
|
1610
1739
|
end
|
|
1611
1740
|
end
|
|
1612
1741
|
|
|
@@ -1614,9 +1743,9 @@ module Rigor
|
|
|
1614
1743
|
# member resolves the same arity and types (otherwise the call sites would have to thread
|
|
1615
1744
|
# per-member binders, which the slice does not support yet). Mismatches degrade to the empty
|
|
1616
1745
|
# array so the binder defaults all params to Dynamic[Top].
|
|
1617
|
-
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)
|
|
1618
1747
|
results = receiver.members.map do |member|
|
|
1619
|
-
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)
|
|
1620
1749
|
end
|
|
1621
1750
|
return [] if results.empty?
|
|
1622
1751
|
return [] unless results.all? { |r| r == results.first }
|
|
@@ -1624,7 +1753,9 @@ module Rigor
|
|
|
1624
1753
|
results.first
|
|
1625
1754
|
end
|
|
1626
1755
|
|
|
1627
|
-
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
|
|
1628
1759
|
descriptor = receiver_descriptor(receiver)
|
|
1629
1760
|
return [] unless descriptor
|
|
1630
1761
|
|
|
@@ -1642,7 +1773,8 @@ module Rigor
|
|
|
1642
1773
|
environment: environment,
|
|
1643
1774
|
receiver: receiver,
|
|
1644
1775
|
receiver_args: receiver_args,
|
|
1645
|
-
method_name: method_name
|
|
1776
|
+
method_name: method_name,
|
|
1777
|
+
keywords_last: keywords_last, positional_splat: splat
|
|
1646
1778
|
)
|
|
1647
1779
|
rescue StandardError
|
|
1648
1780
|
[]
|
|
@@ -1651,7 +1783,7 @@ module Rigor
|
|
|
1651
1783
|
# rubocop:disable Metrics/ParameterLists
|
|
1652
1784
|
def extract_block_param_types(method_definition, class_name:, kind:, args:, type_vars:,
|
|
1653
1785
|
environment: nil, receiver: nil, receiver_args: [],
|
|
1654
|
-
method_name: nil)
|
|
1786
|
+
method_name: nil, keywords_last: false, positional_splat: nil)
|
|
1655
1787
|
# rubocop:enable Metrics/ParameterLists
|
|
1656
1788
|
instance_type = Type::Combinator.nominal_of(class_name)
|
|
1657
1789
|
self_type =
|
|
@@ -1672,12 +1804,25 @@ module Rigor
|
|
|
1672
1804
|
substitute = SelfSubstitute.for(receiver, receiver_args, method_name, args, nil)
|
|
1673
1805
|
self_type = Type::Combinator.nominal_of(class_name, type_args: receiver_args) if substitute
|
|
1674
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
|
+
|
|
1675
1820
|
method_type = OverloadSelector.select(
|
|
1676
1821
|
method_definition,
|
|
1677
1822
|
arg_types: args,
|
|
1678
1823
|
# Overload selection reads a `Dynamic` self's static facet, mirroring the return path;
|
|
1679
1824
|
# the substitution verdict here is built from the receiver's type arguments alone.
|
|
1680
|
-
self_type:
|
|
1825
|
+
self_type: selector_self,
|
|
1681
1826
|
instance_type: instance_type,
|
|
1682
1827
|
type_vars: type_vars,
|
|
1683
1828
|
block_required: true,
|
|
@@ -1685,8 +1830,20 @@ module Rigor
|
|
|
1685
1830
|
)
|
|
1686
1831
|
return [] unless method_type
|
|
1687
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)
|
|
1688
1845
|
block = method_type.respond_to?(:block) ? method_type.block : nil
|
|
1689
|
-
return
|
|
1846
|
+
return nil unless block
|
|
1690
1847
|
|
|
1691
1848
|
translate_block_positional_params(
|
|
1692
1849
|
block,
|
|
@@ -1697,6 +1854,80 @@ module Rigor
|
|
|
1697
1854
|
)
|
|
1698
1855
|
end
|
|
1699
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
|
+
|
|
1700
1931
|
# `RBS::Types::Block#type` is normally an `RBS::Types::Function` carrying the block's parameter
|
|
1701
1932
|
# list; some signatures use `RBS::Types::UntypedFunction` (a `(?)` block) which exposes no
|
|
1702
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
|
|
@@ -18,6 +18,7 @@ require_relative "method_dispatcher/data_folding"
|
|
|
18
18
|
require_relative "method_dispatcher/struct_folding"
|
|
19
19
|
require_relative "method_dispatcher/rbs_dispatch"
|
|
20
20
|
require_relative "method_dispatcher/iterator_dispatch"
|
|
21
|
+
require_relative "method_dispatcher/arithmetic_sequence_elements"
|
|
21
22
|
require_relative "method_dispatcher/reduce_folding"
|
|
22
23
|
require_relative "method_dispatcher/block_folding"
|
|
23
24
|
require_relative "method_dispatcher/array_to_h_folding"
|
|
@@ -101,7 +102,7 @@ module Rigor
|
|
|
101
102
|
# the tier commits the buffer only if it goes on to answer. See {#try_composite_receiver}.
|
|
102
103
|
def resolve(receiver_type:, method_name:, arg_types:, # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ParameterLists
|
|
103
104
|
block_type: nil, environment: nil,
|
|
104
|
-
call_node: nil, scope: nil, plugin_typed_sink: nil)
|
|
105
|
+
call_node: nil, scope: nil, plugin_typed_sink: nil, skip_project_patched: false)
|
|
105
106
|
return nil if receiver_type.nil?
|
|
106
107
|
|
|
107
108
|
# Build the call context once and thread it — unchanged — through every tier
|
|
@@ -205,7 +206,10 @@ module Rigor
|
|
|
205
206
|
# the pre-pass populated `ProjectPatchedMethods` with the `(class, method, kind)` triple;
|
|
206
207
|
# this tier surfaces it as `Dynamic[top]` so the patched call resolves cross-file without
|
|
207
208
|
# `call.undefined-method`.
|
|
208
|
-
patched_result =
|
|
209
|
+
patched_result =
|
|
210
|
+
unless skip_project_patched
|
|
211
|
+
try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment)
|
|
212
|
+
end
|
|
209
213
|
if patched_result
|
|
210
214
|
scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
|
|
211
215
|
return patched_result
|
|
@@ -614,7 +618,7 @@ module Rigor
|
|
|
614
618
|
# `def to_url; "hello"; end` patched onto `String` now resolves `s.to_url` to
|
|
615
619
|
# `Dynamic[Nominal[String]]` instead of the pre-3a `Dynamic[Top]`. Falls back to
|
|
616
620
|
# `Dynamic[Top]` when the heuristic declined (non-literal tail expression).
|
|
617
|
-
def try_project_patched_method(receiver_type, method_name, environment)
|
|
621
|
+
def try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment)
|
|
618
622
|
registry = environment&.project_patched_methods
|
|
619
623
|
return nil if registry.nil? || registry.empty?
|
|
620
624
|
|
|
@@ -624,9 +628,25 @@ module Rigor
|
|
|
624
628
|
kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
|
|
625
629
|
entry = registry.lookup(class_name: class_name, method_name: method_name, kind: kind)
|
|
626
630
|
return nil if entry.nil?
|
|
627
|
-
return Type::Combinator.
|
|
631
|
+
return Type::Combinator.dynamic(entry.return_type) if entry.return_type
|
|
632
|
+
|
|
633
|
+
aliased = project_patched_alias_target(entry, receiver_type, arg_types, block_type, environment)
|
|
634
|
+
aliased ? Type::Combinator.dynamic(aliased) : Type::Combinator.untyped
|
|
635
|
+
end
|
|
628
636
|
|
|
629
|
-
|
|
637
|
+
# Issue #1702 — an alias in a `pre_eval:` patch whose old name no earlier patch binding defines
|
|
638
|
+
# (`alias old_plus +` on Integer, or `alias_method :orig_succ, :succ` ahead of the patch's own `def succ`)
|
|
639
|
+
# names the method the class had before the patch, so it answers what that method answers for the same
|
|
640
|
+
# arguments, behind the `Dynamic` every patched method carries. The re-entry skips this tier: a later patch
|
|
641
|
+
# `def` of the old name is not the body the alias copied, and the skip is also what ends an alias cycle. It
|
|
642
|
+
# carries no call node or scope, so it answers a type and records nothing at the call site.
|
|
643
|
+
def project_patched_alias_target(entry, receiver_type, arg_types, block_type, environment)
|
|
644
|
+
return nil if entry.alias_of.nil?
|
|
645
|
+
|
|
646
|
+
resolve(
|
|
647
|
+
receiver_type: receiver_type, method_name: entry.alias_of, arg_types: arg_types,
|
|
648
|
+
block_type: block_type, environment: environment, skip_project_patched: true
|
|
649
|
+
)
|
|
630
650
|
end
|
|
631
651
|
|
|
632
652
|
# ADR-10 slice 2b-ii. Consults the per-run `Analysis::DependencySourceInference::Index`
|
|
@@ -847,7 +867,8 @@ module Rigor
|
|
|
847
867
|
private_constant :STDLIB_SINGLETON_FOLDERS
|
|
848
868
|
|
|
849
869
|
PRECISE_TIERS_TAIL = Ractor.make_shareable([
|
|
850
|
-
MethodFolding, ReduceFolding, ArrayToHFolding, HashTransformKeysFolding, BlockFolding
|
|
870
|
+
MethodFolding, ReduceFolding, ArrayToHFolding, HashTransformKeysFolding, BlockFolding,
|
|
871
|
+
ArithmeticSequenceElements
|
|
851
872
|
].freeze)
|
|
852
873
|
private_constant :PRECISE_TIERS_TAIL
|
|
853
874
|
|
|
@@ -1104,14 +1125,23 @@ module Rigor
|
|
|
1104
1125
|
# `instance_of?(self)` later as the rule catalogue grows; for now only `class` is handled.
|
|
1105
1126
|
def try_meta_introspection(receiver_type, method_name, arg_types = [], context = nil)
|
|
1106
1127
|
case method_name
|
|
1107
|
-
when :class then meta_class(receiver_type)
|
|
1128
|
+
when :class then meta_class(receiver_type, context&.environment)
|
|
1108
1129
|
when :new then meta_new(receiver_type, arg_types, context)
|
|
1109
1130
|
end
|
|
1110
1131
|
end
|
|
1111
1132
|
|
|
1112
|
-
def meta_class(receiver_type)
|
|
1133
|
+
def meta_class(receiver_type, environment = nil)
|
|
1113
1134
|
case receiver_type
|
|
1114
|
-
when Type::Nominal
|
|
1135
|
+
when Type::Nominal
|
|
1136
|
+
# An instance of module `M` is an instance of some class that includes `M`, never of `M`
|
|
1137
|
+
# itself: `self.class` in `M`'s instance method is that includer, which no RBS type names.
|
|
1138
|
+
# `Dynamic[Class]` keeps the surface every class shares (`name`, `ancestors`) and answers
|
|
1139
|
+
# nothing for the includer's own singleton methods.
|
|
1140
|
+
if environment&.rbs_module?(receiver_type.class_name)
|
|
1141
|
+
return Type::Combinator.dynamic(Type::Combinator.nominal_of("Class"))
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
Type::Combinator.singleton_of(receiver_type.class_name)
|
|
1115
1145
|
when Type::Constant then constant_metaclass(receiver_type.value)
|
|
1116
1146
|
end
|
|
1117
1147
|
end
|
|
@@ -1555,13 +1585,16 @@ module Rigor
|
|
|
1555
1585
|
# method definition, or selected overload does not provide statically declared block
|
|
1556
1586
|
# parameter types. Callers MUST treat the empty array as "no information"; the binder
|
|
1557
1587
|
# falls back to `Dynamic[Top]` for every parameter slot in that case.
|
|
1588
|
+
#
|
|
1589
|
+
# `call_node` lets the overload selector read the call's keyword arguments as keywords (#1727, #1737), as
|
|
1590
|
+
# the return path does; without it they are read positionally.
|
|
1558
1591
|
def expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil,
|
|
1559
|
-
scope: nil)
|
|
1592
|
+
scope: nil, call_node: nil)
|
|
1560
1593
|
return [] if receiver_type.nil?
|
|
1561
1594
|
|
|
1562
1595
|
context = CallContext.build(
|
|
1563
1596
|
receiver: receiver_type, method_name: method_name,
|
|
1564
|
-
args: arg_types, environment: environment, scope: scope
|
|
1597
|
+
args: arg_types, environment: environment, scope: scope, call_node: call_node
|
|
1565
1598
|
)
|
|
1566
1599
|
iterator_result = IteratorDispatch.block_param_types(context)
|
|
1567
1600
|
return iterator_result if iterator_result
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
|
+
require_relative "../source/literals"
|
|
4
5
|
require_relative "../source/node_children"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
@@ -204,7 +205,7 @@ module Rigor
|
|
|
204
205
|
|
|
205
206
|
# The Symbol a `:name` or `"name"` literal argument names, or nil.
|
|
206
207
|
def symbol_argument_name(arg)
|
|
207
|
-
|
|
208
|
+
Source::Literals.symbol_or_string(arg)
|
|
208
209
|
end
|
|
209
210
|
|
|
210
211
|
private_class_method :prescan_call, :prescan_eval_call, :symbol_argument_name
|