rigortype 0.4.7 → 0.4.8
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 +3 -1
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +29 -0
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/docs/handbook/01-getting-started.md +1 -1
- data/docs/handbook/11-sig-gen.md +35 -0
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +307 -91
- data/docs/manual/03-configuration.md +41 -3
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +8 -0
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +15 -6
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +598 -99
- data/lib/rigor/analysis/crash_signature.rb +43 -1
- data/lib/rigor/analysis/dependency_recorder.rb +233 -76
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +279 -37
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +37 -1
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
- data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
- data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner.rb +222 -39
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
- data/lib/rigor/analysis/worker_session.rb +42 -41
- data/lib/rigor/bleeding_edge.rb +19 -4
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +66 -7
- data/lib/rigor/cache/rbs_descriptor.rb +37 -2
- data/lib/rigor/cache/rbs_environment.rb +5 -1
- data/lib/rigor/cli/baseline_command.rb +15 -2
- data/lib/rigor/cli/check_command.rb +4 -2
- data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
- data/lib/rigor/cli/lens_command.rb +17 -2
- data/lib/rigor/cli/sig_gen_command.rb +8 -0
- data/lib/rigor/cli/skill_describe.rb +1 -0
- data/lib/rigor/cli.rb +5 -2
- data/lib/rigor/config_audit.rb +32 -18
- data/lib/rigor/configuration/severity_profile.rb +9 -0
- data/lib/rigor/configuration.rb +32 -5
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/default_libraries.rb +17 -0
- data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
- data/lib/rigor/environment/rbs_loader.rb +312 -63
- data/lib/rigor/environment.rb +131 -35
- data/lib/rigor/inference/acceptance.rb +50 -15
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_run_count.rb +9 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +56 -15
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/def_node_resolver.rb +32 -14
- data/lib/rigor/inference/def_return_typer.rb +1 -1
- data/lib/rigor/inference/definer_resolution.rb +15 -7
- data/lib/rigor/inference/definers_index.rb +72 -16
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1459 -287
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +8 -6
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +57 -8
- data/lib/rigor/inference/ivar_writer_census.rb +106 -11
- data/lib/rigor/inference/key_presence_guard.rb +51 -25
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +63 -15
- data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
- data/lib/rigor/inference/match_rebinding.rb +21 -8
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher.rb +66 -18
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +13 -2
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_widening.rb +44 -4
- data/lib/rigor/inference/narrowing.rb +201 -27
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +133 -8
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +28 -10
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +76 -22
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2079 -183
- data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +970 -250
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
- data/lib/rigor/inference/toplevel_def_census.rb +14 -6
- data/lib/rigor/inference/unknown_store_widening.rb +1 -1
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -10
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/io_boundary.rb +25 -3
- data/lib/rigor/plugin/manifest.rb +7 -3
- data/lib/rigor/plugin/registry.rb +13 -2
- data/lib/rigor/plugin/trust_policy.rb +46 -0
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/reflection/constant_ancestors.rb +79 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +18 -1
- data/lib/rigor/run_scoped_slots.rb +20 -20
- data/lib/rigor/scope/discovery_index.rb +149 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +900 -55
- data/lib/rigor/scope.rb +307 -34
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +14 -1
- data/lib/rigor/sig_gen/generator.rb +222 -13
- data/lib/rigor/sig_gen/method_candidate.rb +14 -0
- data/lib/rigor/sig_gen/observation_collector.rb +11 -2
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
- data/lib/rigor/sig_gen/writer.rb +21 -0
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/combinator.rb +164 -8
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -3
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/plugin/base.rbs +53 -19
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -3
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +22 -3
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +48 -2
- data/lib/rigor/inference/memo_audit.rb +0 -55
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# Issue #2828 — how overload selection reads a parameter spelled `singleton(T)` over a method-level type
|
|
9
|
+
# variable `T`. RBS parses it as a class singleton whose name is the bare, relative `T`, and the
|
|
10
|
+
# translator reads that as a class named `T`: an unloadable class whose singleton refuses every class object
|
|
11
|
+
# the analyzer process has loaded (`Acceptance` rules a loaded class out of an unloadable target's
|
|
12
|
+
# subtypes). With `Resolv` loaded, `Resolv::DNS#getresources: [T < Resource] (dns_name, singleton(T)) ->
|
|
13
|
+
# Array[T] | ...` then lost to the base overload.
|
|
14
|
+
#
|
|
15
|
+
# Selection instead reads the parameter as "a class object" the variable admits: `singleton(X)` for a
|
|
16
|
+
# class upper bound `X`, and `Module` otherwise, which takes any class or module object and refuses an
|
|
17
|
+
# instance. Only names the overload declares in its own `type_params` are read this way; any other
|
|
18
|
+
# unresolved `singleton(X)` keeps the translator's reading.
|
|
19
|
+
#
|
|
20
|
+
# {.selecting} hands the selector the overloads rewritten where a parameter needs it and maps the selected
|
|
21
|
+
# rewrites back, so the return translation and the argument binding (`RbsDispatch#singleton_param_binding`)
|
|
22
|
+
# still read the declared `singleton(T)`.
|
|
23
|
+
module SingletonTypeParams
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# Yields the overloads to select among and answers the block's selection, with each rewrite mapped back to
|
|
27
|
+
# its declaration. Nearly every method needs no rewrite, and then `declared` is yielded as is.
|
|
28
|
+
def selecting(declared)
|
|
29
|
+
return yield(declared) unless declared.any? { |method_type| rewrites?(method_type) }
|
|
30
|
+
|
|
31
|
+
originals = {}.compare_by_identity
|
|
32
|
+
rewritten = declared.map do |method_type|
|
|
33
|
+
next method_type unless rewrites?(method_type)
|
|
34
|
+
|
|
35
|
+
rewrite = rewrite(method_type)
|
|
36
|
+
originals[rewrite] = method_type
|
|
37
|
+
rewrite
|
|
38
|
+
end
|
|
39
|
+
yield(rewritten).map { |method_type| originals.fetch(method_type, method_type) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def rewrites?(method_type)
|
|
43
|
+
params = method_type.respond_to?(:type_params) ? method_type.type_params : nil
|
|
44
|
+
return false if params.nil? || params.empty?
|
|
45
|
+
|
|
46
|
+
fun = method_type.type
|
|
47
|
+
return false unless fun.respond_to?(:required_positionals)
|
|
48
|
+
|
|
49
|
+
any_variable_singleton?(fun.required_positionals, params) ||
|
|
50
|
+
any_variable_singleton?(fun.optional_positionals, params) ||
|
|
51
|
+
any_variable_singleton?(fun.trailing_positionals, params)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def any_variable_singleton?(list, type_params)
|
|
55
|
+
list.any? { |param| variable_singleton?(param.type, type_params) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def variable_singleton?(type, type_params)
|
|
59
|
+
return false unless type.is_a?(RBS::Types::ClassSingleton)
|
|
60
|
+
|
|
61
|
+
name = type.name
|
|
62
|
+
return false if name.absolute? || !name.namespace.empty?
|
|
63
|
+
|
|
64
|
+
type_params.any? { |type_param| type_param.name == name.name }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# One parameter of `method_type` as selection reads it: `call.argument-type-mismatch` checks a call the
|
|
68
|
+
# same way, and otherwise declines every overload with a `singleton(T)` parameter as naming a class nothing
|
|
69
|
+
# defines.
|
|
70
|
+
def selection_param(param, method_type)
|
|
71
|
+
type_params = method_type.type_params
|
|
72
|
+
return param unless variable_singleton?(param.type, type_params)
|
|
73
|
+
|
|
74
|
+
param.map_type { |type| class_object_type(type.name.name, type_params) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def rewrite(method_type)
|
|
78
|
+
type_params = method_type.type_params
|
|
79
|
+
fun = method_type.type.map_type do |type|
|
|
80
|
+
variable_singleton?(type, type_params) ? class_object_type(type.name.name, type_params) : type
|
|
81
|
+
end
|
|
82
|
+
RBS::MethodType.new(type_params: type_params, type: fun, block: method_type.block,
|
|
83
|
+
location: method_type.location)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Built per rewrite rather than held in a constant: an RBS type is not `Ractor.shareable?` (#1064), and a
|
|
87
|
+
# rewrite happens only for the few overloads that spell `singleton(T)`.
|
|
88
|
+
def module_type
|
|
89
|
+
RBS::Types::ClassInstance.new(
|
|
90
|
+
name: RBS::TypeName.new(name: :Module, namespace: RBS::Namespace.root), args: [], location: nil
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def class_object_type(name, type_params)
|
|
95
|
+
type_param = type_params.find { |tp| tp.name == name }
|
|
96
|
+
bound = type_param.upper_bound
|
|
97
|
+
return module_type unless bound.is_a?(RBS::Types::ClassInstance)
|
|
98
|
+
|
|
99
|
+
RBS::Types::ClassSingleton.new(name: bound.name, location: nil)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../reflection"
|
|
4
|
+
require_relative "../typing_state"
|
|
4
5
|
require_relative "../type"
|
|
5
6
|
require_relative "../flow_contribution"
|
|
6
7
|
require_relative "../flow_contribution/merger"
|
|
@@ -18,6 +19,7 @@ require_relative "method_dispatcher/shape_dispatch"
|
|
|
18
19
|
require_relative "method_dispatcher/data_folding"
|
|
19
20
|
require_relative "method_dispatcher/struct_folding"
|
|
20
21
|
require_relative "method_dispatcher/rbs_dispatch"
|
|
22
|
+
require_relative "method_dispatcher/declared_constructor"
|
|
21
23
|
require_relative "method_dispatcher/iterator_dispatch"
|
|
22
24
|
require_relative "method_dispatcher/arithmetic_sequence_elements"
|
|
23
25
|
require_relative "method_dispatcher/reduce_folding"
|
|
@@ -195,7 +197,7 @@ module Rigor
|
|
|
195
197
|
rbs_result = RbsDispatch.try_dispatch(context)
|
|
196
198
|
if rbs_result
|
|
197
199
|
record_boundary_cross_if_applicable(receiver_type, method_name, rbs_result, environment)
|
|
198
|
-
if
|
|
200
|
+
if Type::Combinator.untyped?(rbs_result)
|
|
199
201
|
scope&.record_dynamic_origin(call_node, DynamicOrigin::EXPLICIT_UNTYPED)
|
|
200
202
|
end
|
|
201
203
|
return rbs_result
|
|
@@ -826,7 +828,7 @@ module Rigor
|
|
|
826
828
|
end
|
|
827
829
|
|
|
828
830
|
def rbs_result_untyped?(rbs_result)
|
|
829
|
-
|
|
831
|
+
Type::Combinator.untyped?(rbs_result)
|
|
830
832
|
end
|
|
831
833
|
|
|
832
834
|
def rbs_display_for(rbs_result)
|
|
@@ -1070,13 +1072,29 @@ module Rigor
|
|
|
1070
1072
|
scope = context.scope
|
|
1071
1073
|
return false if scope.nil?
|
|
1072
1074
|
|
|
1075
|
+
class_name = receiver_type.class_name
|
|
1076
|
+
method_name = context.method_name
|
|
1077
|
+
if MemoAudit.enabled? && MemoAudit.suspended?
|
|
1078
|
+
return external_ancestor_declares?(class_name, method_name, scope,
|
|
1079
|
+
environment)
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1073
1082
|
memo = external_ancestor_memo(scope, environment)
|
|
1074
|
-
key = [
|
|
1075
|
-
|
|
1083
|
+
key = [class_name, method_name]
|
|
1084
|
+
if memo.key?(key)
|
|
1085
|
+
return MemoAudit.check("user class fallback external ancestor", memo[key], class_name, method_name) do
|
|
1086
|
+
external_ancestor_declares?(class_name, method_name, scope, environment)
|
|
1087
|
+
end
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
memo[key] = MemoAudit.key_audit("user class fallback external ancestor") do
|
|
1091
|
+
external_ancestor_declares?(class_name, method_name, scope, environment)
|
|
1092
|
+
end
|
|
1093
|
+
end
|
|
1076
1094
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1095
|
+
def external_ancestor_declares?(class_name, method_name, scope, environment)
|
|
1096
|
+
!ExternalAncestorResolution.resolve(class_name, method_name, :instance,
|
|
1097
|
+
scope: scope, environment: environment, record_dependencies: false).nil?
|
|
1080
1098
|
end
|
|
1081
1099
|
|
|
1082
1100
|
# Issue #2284 — a class object whose singleton chain holds a project definer of the name (its own `def
|
|
@@ -1101,43 +1119,67 @@ module Rigor
|
|
|
1101
1119
|
end
|
|
1102
1120
|
|
|
1103
1121
|
def project_defines_name?(scope, method_name)
|
|
1122
|
+
if MemoAudit.enabled? && MemoAudit.suspended?
|
|
1123
|
+
return BlockCallTiming.project_defines_anywhere?(method_name,
|
|
1124
|
+
scope)
|
|
1125
|
+
end
|
|
1126
|
+
|
|
1104
1127
|
memo = defines_name_memo(scope)
|
|
1105
1128
|
if memo.key?(method_name)
|
|
1106
1129
|
Analysis::DependencyRecorder.read_missing(:defines, method_name) if Analysis::DependencyRecorder.active?
|
|
1107
|
-
return memo[method_name]
|
|
1130
|
+
return MemoAudit.check("user class fallback defines name", memo[method_name], method_name) do
|
|
1131
|
+
BlockCallTiming.project_defines_anywhere?(method_name, scope)
|
|
1132
|
+
end
|
|
1108
1133
|
end
|
|
1109
1134
|
|
|
1110
|
-
memo[method_name] =
|
|
1135
|
+
memo[method_name] = MemoAudit.key_audit("user class fallback defines name") do
|
|
1136
|
+
BlockCallTiming.project_defines_anywhere?(method_name, scope)
|
|
1137
|
+
end
|
|
1111
1138
|
end
|
|
1112
1139
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1140
|
+
# ADR-129 WD1 — a run-scoped Memo: one slot keyed on the identity of the discovery index and the environment,
|
|
1141
|
+
# the only inputs the name table's answers read.
|
|
1142
|
+
DEFINES_NAME_MEMO = TypingState.memo(
|
|
1143
|
+
:user_class_fallback_defines_name,
|
|
1144
|
+
run_scoped: true, audit: "user class fallback defines name",
|
|
1145
|
+
reason: "one [discovery, environment, names] slot keyed on the identities of the discovery " \
|
|
1146
|
+
"index and the environment; each name's answer reads only them"
|
|
1147
|
+
)
|
|
1148
|
+
private_constant :DEFINES_NAME_MEMO
|
|
1115
1149
|
|
|
1116
1150
|
# One slot per discovery index and environment, as {#external_ancestor_memo} keeps its own: the answer reads
|
|
1117
1151
|
# only the frozen discovery tables and the environment's `pre_eval:` patches.
|
|
1118
1152
|
def defines_name_memo(scope)
|
|
1119
1153
|
discovery = scope.discovery
|
|
1120
1154
|
environment = scope.environment
|
|
1121
|
-
|
|
1155
|
+
state = TypingState.current
|
|
1156
|
+
slot = state[DEFINES_NAME_MEMO]
|
|
1122
1157
|
unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
|
|
1123
1158
|
slot = [discovery, environment, {}]
|
|
1124
|
-
|
|
1159
|
+
state[DEFINES_NAME_MEMO] = slot
|
|
1125
1160
|
end
|
|
1126
1161
|
slot[2]
|
|
1127
1162
|
end
|
|
1128
1163
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1164
|
+
# ADR-129 WD1 — a run-scoped Memo, keyed as {DEFINES_NAME_MEMO} is.
|
|
1165
|
+
EXTERNAL_ANCESTOR_MEMO = TypingState.memo(
|
|
1166
|
+
:user_class_fallback_external_ancestor,
|
|
1167
|
+
run_scoped: true, audit: "user class fallback external ancestor",
|
|
1168
|
+
reason: "one [discovery, environment, answers] slot keyed on the identities of the " \
|
|
1169
|
+
"discovery index and the RBS environment, the only inputs the walk reads"
|
|
1170
|
+
)
|
|
1171
|
+
private_constant :EXTERNAL_ANCESTOR_MEMO
|
|
1131
1172
|
|
|
1132
1173
|
# One slot per discovery index and environment, as `RbsDispatch#ancestor_memo_slot` keeps its own: the answer
|
|
1133
1174
|
# reads only the frozen discovery tables and the RBS environment, and its walk is withheld from dependency
|
|
1134
1175
|
# recording, so it may be served again for the same pair.
|
|
1135
1176
|
def external_ancestor_memo(scope, environment)
|
|
1136
1177
|
discovery = scope.discovery
|
|
1137
|
-
|
|
1178
|
+
state = TypingState.current
|
|
1179
|
+
slot = state[EXTERNAL_ANCESTOR_MEMO]
|
|
1138
1180
|
unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
|
|
1139
1181
|
slot = [discovery, environment, {}]
|
|
1140
|
-
|
|
1182
|
+
state[EXTERNAL_ANCESTOR_MEMO] = slot
|
|
1141
1183
|
end
|
|
1142
1184
|
slot[2]
|
|
1143
1185
|
end
|
|
@@ -1364,7 +1406,13 @@ module Rigor
|
|
|
1364
1406
|
class_new_lift = class_new_lift(receiver_type.class_name, arg_types, context)
|
|
1365
1407
|
return class_new_lift if class_new_lift
|
|
1366
1408
|
|
|
1367
|
-
|
|
1409
|
+
declared_or_nominal(receiver_type.class_name, context)
|
|
1410
|
+
end
|
|
1411
|
+
|
|
1412
|
+
# Issue #2884 — the `meta_new` tail: a declared `def self.new` ({DeclaredConstructor}), else `Nominal[X]`.
|
|
1413
|
+
def declared_or_nominal(class_name, context)
|
|
1414
|
+
declared = context && DeclaredConstructor.try_dispatch(class_name, context)
|
|
1415
|
+
declared || Type::Combinator.nominal_of(class_name)
|
|
1368
1416
|
end
|
|
1369
1417
|
|
|
1370
1418
|
# `Struct.new(:a, :b)` synthesises an anonymous Struct *subclass* (a class object), not a
|
|
@@ -54,6 +54,114 @@ module Rigor
|
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
# Issue #2681 — which declared type, if any, one RBS overload hands a `def`'s parameter slot when the def's shape
|
|
58
|
+
# need not match the declaration's (an override bound from its ancestor, or a source `def` whose RBS was written
|
|
59
|
+
# for another shape). A slot takes a type only where the two shapes line up:
|
|
60
|
+
#
|
|
61
|
+
# - a leading positional (required or optional) takes the declared leading positional at the same ABSOLUTE
|
|
62
|
+
# position, and a post positional the declared trailing one, provided both sides have as many post as
|
|
63
|
+
# trailing positionals: Ruby fills the leading parameters left to right on both sides, so the argument a call
|
|
64
|
+
# puts at a leading position reaches the slot at the same one;
|
|
65
|
+
# - `*rest` takes the declared rest only when the def has exactly the declared leading positionals;
|
|
66
|
+
# - a keyword takes the declared keyword of its name, and `**kw` the declared keyword rest only when the def
|
|
67
|
+
# names every declared keyword.
|
|
68
|
+
#
|
|
69
|
+
# Any other overlap is {MISALIGNED}, and leaves the slot `Dynamic[Top]`. {NOT_PASSED} is an overload that passes
|
|
70
|
+
# the slot nothing (`def first(n)` under `Array#first`'s `()`), which the binder skips.
|
|
71
|
+
module SlotAlignment
|
|
72
|
+
module_function
|
|
73
|
+
|
|
74
|
+
MISALIGNED = :misaligned
|
|
75
|
+
NOT_PASSED = :not_passed
|
|
76
|
+
|
|
77
|
+
# `accepts_keywords` is false for a def with no keyword parameter and no `**`, `**nil` or `...`: Ruby passes a
|
|
78
|
+
# call's keywords to such a def as a Hash in one more positional argument.
|
|
79
|
+
Shape = Data.define(:requireds, :leading, :posts, :keywords, :accepts_keywords)
|
|
80
|
+
|
|
81
|
+
def shape_of(params_node)
|
|
82
|
+
return Shape.new(0, 0, 0, [].freeze, false) if params_node.nil?
|
|
83
|
+
|
|
84
|
+
requireds = params_node.requireds.size
|
|
85
|
+
keywords = params_node.keywords.map(&:name).freeze
|
|
86
|
+
accepts_keywords = !keywords.empty? || !params_node.keyword_rest.nil?
|
|
87
|
+
Shape.new(requireds, requireds + params_node.optionals.size, params_node.posts.size, keywords,
|
|
88
|
+
accepts_keywords)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The RBS type the overload `function` declares for `slot`, {MISALIGNED} or {NOT_PASSED}.
|
|
92
|
+
def declared(function, slot, shape)
|
|
93
|
+
return MISALIGNED if keyword_hash_reaches?(function, slot, shape)
|
|
94
|
+
|
|
95
|
+
case slot.kind
|
|
96
|
+
when :required_positional then leading(function, shape, slot.index)
|
|
97
|
+
when :optional_positional then leading(function, shape, shape.requireds + slot.index)
|
|
98
|
+
when :trailing_positional then trailing(function, shape, slot.index)
|
|
99
|
+
when :rest_positional then rest(function, shape)
|
|
100
|
+
when :required_keyword, :optional_keyword then keyword(function, slot.name)
|
|
101
|
+
when :rest_keyword then rest_keyword(function, shape)
|
|
102
|
+
else NOT_PASSED
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Whether the overload's keywords may reach `slot` as a positional Hash: the def accepts no keywords, so a
|
|
107
|
+
# call's keywords arrive as one more positional argument, which lands past the declared required leading
|
|
108
|
+
# positionals, in the `*rest`, or (shifting every post positional) in a post positional.
|
|
109
|
+
def keyword_hash_reaches?(function, slot, shape)
|
|
110
|
+
return false if shape.accepts_keywords
|
|
111
|
+
return false if function.required_keywords.empty? && function.optional_keywords.empty? &&
|
|
112
|
+
function.rest_keywords.nil?
|
|
113
|
+
|
|
114
|
+
case slot.kind
|
|
115
|
+
when :required_positional then slot.index >= function.required_positionals.size
|
|
116
|
+
when :optional_positional then shape.requireds + slot.index >= function.required_positionals.size
|
|
117
|
+
when :rest_positional, :trailing_positional then true
|
|
118
|
+
else false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def leading(function, shape, position)
|
|
123
|
+
return MISALIGNED unless function.trailing_positionals.size == shape.posts
|
|
124
|
+
|
|
125
|
+
required = function.required_positionals
|
|
126
|
+
return required[position].type if position < required.size
|
|
127
|
+
|
|
128
|
+
optional = function.optional_positionals[position - required.size]
|
|
129
|
+
return optional.type if optional
|
|
130
|
+
|
|
131
|
+
function.rest_positionals ? MISALIGNED : NOT_PASSED
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def trailing(function, shape, index)
|
|
135
|
+
return MISALIGNED unless function.trailing_positionals.size == shape.posts
|
|
136
|
+
|
|
137
|
+
function.trailing_positionals[index].type
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def rest(function, shape)
|
|
141
|
+
return MISALIGNED unless function.trailing_positionals.size == shape.posts
|
|
142
|
+
|
|
143
|
+
declared_leading = function.required_positionals.size + function.optional_positionals.size
|
|
144
|
+
return MISALIGNED if shape.leading < declared_leading
|
|
145
|
+
return NOT_PASSED if function.rest_positionals.nil?
|
|
146
|
+
|
|
147
|
+
shape.leading == declared_leading ? function.rest_positionals.type : MISALIGNED
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def keyword(function, name)
|
|
151
|
+
declared = function.required_keywords[name] || function.optional_keywords[name]
|
|
152
|
+
return declared.type if declared
|
|
153
|
+
|
|
154
|
+
function.rest_keywords ? MISALIGNED : NOT_PASSED
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def rest_keyword(function, shape)
|
|
158
|
+
declared = function.required_keywords.keys + function.optional_keywords.keys
|
|
159
|
+
return MISALIGNED unless declared.all? { |name| shape.keywords.include?(name) }
|
|
160
|
+
|
|
161
|
+
function.rest_keywords ? function.rest_keywords.type : NOT_PASSED
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
57
165
|
class MethodParameterBinder
|
|
58
166
|
# @param class_path — the qualified name of the class the method is defined in
|
|
59
167
|
# (e.g., `"Foo::Bar"`), or `nil` for a top-level `def` outside any class. When `nil` (or
|
|
@@ -64,11 +172,15 @@ module Rigor
|
|
|
64
172
|
# @param source_path — the project-relative path of the file the method is
|
|
65
173
|
# defined in. Used to match ADR-28 path-scoped protocol contracts; `nil` (the default
|
|
66
174
|
# for synthetic / probe scopes) disables the contract tier.
|
|
67
|
-
def
|
|
175
|
+
# @param scope — the scope the `def` is written in. When supplied, a `def` on a Ruby-source class with no RBS
|
|
176
|
+
# of its own binds the parameters of the declaration it overrides on an ADR-43 allow-listed ancestor (#2681);
|
|
177
|
+
# `nil` keeps that class's parameters `Dynamic[Top]`.
|
|
178
|
+
def initialize(environment:, class_path:, singleton:, source_path: nil, scope: nil)
|
|
68
179
|
@environment = environment
|
|
69
180
|
@class_path = class_path
|
|
70
181
|
@singleton = singleton
|
|
71
182
|
@source_path = source_path
|
|
183
|
+
@scope = scope
|
|
72
184
|
end
|
|
73
185
|
|
|
74
186
|
# @return ordered map from parameter name to bound type.
|
|
@@ -79,7 +191,9 @@ module Rigor
|
|
|
79
191
|
|
|
80
192
|
rbs_method = lookup_rbs_method(def_node)
|
|
81
193
|
if rbs_method
|
|
82
|
-
|
|
194
|
+
unless rbs_method.method_types.empty?
|
|
195
|
+
apply_rbs_overloads(types, slots, rbs_method.method_types, SlotAlignment.shape_of(def_node.parameters))
|
|
196
|
+
end
|
|
83
197
|
# `rigor:v1:param: <name> <refinement>` annotations tighten the bound type for matching
|
|
84
198
|
# slots. Applied after the RBS-overload pass so the override is the authoritative
|
|
85
199
|
# answer regardless of what the RBS signature declared.
|
|
@@ -186,15 +300,17 @@ module Rigor
|
|
|
186
300
|
# `def self.foo` always means a singleton method on the immediate enclosing class. `def
|
|
187
301
|
# foo` inside `class << self` is also a singleton method (the StatementEvaluator threads
|
|
188
302
|
# the `singleton:` flag through this case).
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
303
|
+
# ADR-43 (#2681) — a source-only subclass of an allow-listed RBS-complete ancestor (`Rigor::Plugin::Base`)
|
|
304
|
+
# overriding one of its hooks binds the hook's declared parameters, as an empty RBS declaration of the
|
|
305
|
+
# subclass would through RBS's own inheritance.
|
|
306
|
+
kind = def_node.receiver.is_a?(Prism::SelfNode) || @singleton ? :singleton : :instance
|
|
307
|
+
MethodDispatcher::RbsDispatch.def_declaration(@environment, @class_path, kind, method_name, @scope)
|
|
194
308
|
end
|
|
195
309
|
|
|
196
310
|
# Bind each parameter slot to the union of the matching parameter types across every
|
|
197
|
-
# overload that *has* that slot
|
|
311
|
+
# overload that *has* that slot, where the def's parameter shape lines up with the overload's
|
|
312
|
+
# ({SlotAlignment}); one overload that does not line up leaves the slot `Dynamic[Top]`.
|
|
313
|
+
# Overloads that pass the slot no argument (e.g., `Array#first` has
|
|
198
314
|
# both `()` and `(int)` overloads — only the second matches a `def first(n)` redefinition)
|
|
199
315
|
# are silently skipped, so the binder defaults to the most informative type the RBS
|
|
200
316
|
# signature provides without having to know which overload the runtime will pick.
|
|
@@ -203,14 +319,14 @@ module Rigor
|
|
|
203
319
|
# accepts any argument in every slot, so no slot can be narrowed below `Dynamic[Top]`: binding a typed
|
|
204
320
|
# sibling overload's parameter type would read a value that overload legitimately passes as a
|
|
205
321
|
# contradiction inside the body. Every slot keeps its default; the declared return type still applies.
|
|
206
|
-
def apply_rbs_overloads(types, slots, method_types)
|
|
322
|
+
def apply_rbs_overloads(types, slots, method_types, shape)
|
|
207
323
|
return unless method_types.all? { |mt| mt.type.respond_to?(:required_positionals) }
|
|
208
324
|
|
|
209
325
|
slots.each do |slot|
|
|
210
326
|
next if slot.name.nil?
|
|
211
327
|
|
|
212
|
-
translated = collect_translated_types(method_types, slot)
|
|
213
|
-
next if translated.empty?
|
|
328
|
+
translated = collect_translated_types(method_types, slot, shape)
|
|
329
|
+
next if translated.nil? || translated.empty?
|
|
214
330
|
|
|
215
331
|
types[slot.name] = build_slot_type(translated, slot.kind)
|
|
216
332
|
end
|
|
@@ -272,10 +388,15 @@ module Rigor
|
|
|
272
388
|
slots.find { |slot| POSITIONAL_KINDS.include?(slot.kind) && slot.index == index }
|
|
273
389
|
end
|
|
274
390
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
391
|
+
# The translated types the overloads declare for `slot`, or nil when one of them does not line up with the
|
|
392
|
+
# def's parameter shape ({SlotAlignment}): that overload may hand the slot an argument of another type.
|
|
393
|
+
def collect_translated_types(method_types, slot, shape)
|
|
394
|
+
rbs_types = []
|
|
395
|
+
method_types.each do |mt|
|
|
396
|
+
declared = SlotAlignment.declared(mt.type, slot, shape)
|
|
397
|
+
return nil if declared.equal?(SlotAlignment::MISALIGNED)
|
|
398
|
+
|
|
399
|
+
rbs_types << declared unless declared.equal?(SlotAlignment::NOT_PASSED)
|
|
279
400
|
end
|
|
280
401
|
rbs_types.map { |t| translate_with_self(t) }.uniq
|
|
281
402
|
end
|
|
@@ -285,47 +406,6 @@ module Rigor
|
|
|
285
406
|
wrap_for_kind(bound, kind)
|
|
286
407
|
end
|
|
287
408
|
|
|
288
|
-
# Dispatch table from slot kind to a small lambda that pulls the matching RBS parameter
|
|
289
|
-
# type out of an `RBS::Types::Function`. The hash keeps `rbs_type_for_slot` linear (one
|
|
290
|
-
# lookup, one call) so the cyclomatic-complexity budget does not balloon as future slices
|
|
291
|
-
# add more parameter kinds (e.g., `**Symbol kw` is a candidate for a stricter route in
|
|
292
|
-
# Slice 5+).
|
|
293
|
-
# Match keyword parameters by name across both required and optional keyword maps. RBS may
|
|
294
|
-
# declare a keyword as optional (`?by:`) while the Ruby `def` lists it as required (or
|
|
295
|
-
# vice versa); the binding is by-name regardless of which side defines it.
|
|
296
|
-
KEYWORD_PROVIDER = Ractor.make_shareable(lambda do |fn, slot|
|
|
297
|
-
fn.required_keywords[slot.name]&.type || fn.optional_keywords[slot.name]&.type
|
|
298
|
-
end)
|
|
299
|
-
private_constant :KEYWORD_PROVIDER
|
|
300
|
-
|
|
301
|
-
RBS_TYPE_PROVIDERS = Ractor.make_shareable({
|
|
302
|
-
required_positional: Ractor.make_shareable(lambda { |fn, slot|
|
|
303
|
-
fn.required_positionals[slot.index]&.type
|
|
304
|
-
}),
|
|
305
|
-
optional_positional: Ractor.make_shareable(lambda { |fn, slot|
|
|
306
|
-
fn.optional_positionals[slot.index]&.type
|
|
307
|
-
}),
|
|
308
|
-
rest_positional: Ractor.make_shareable(lambda { |fn, _slot|
|
|
309
|
-
fn.rest_positionals&.type
|
|
310
|
-
}),
|
|
311
|
-
trailing_positional: Ractor.make_shareable(lambda { |fn, slot|
|
|
312
|
-
fn.trailing_positionals[slot.index]&.type
|
|
313
|
-
}),
|
|
314
|
-
required_keyword: KEYWORD_PROVIDER,
|
|
315
|
-
optional_keyword: KEYWORD_PROVIDER,
|
|
316
|
-
rest_keyword: Ractor.make_shareable(lambda { |fn, _slot|
|
|
317
|
-
fn.rest_keywords&.type
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
private_constant :RBS_TYPE_PROVIDERS
|
|
321
|
-
|
|
322
|
-
def rbs_type_for_slot(function, slot)
|
|
323
|
-
provider = RBS_TYPE_PROVIDERS[slot.kind]
|
|
324
|
-
return nil unless provider
|
|
325
|
-
|
|
326
|
-
provider.call(function, slot)
|
|
327
|
-
end
|
|
328
|
-
|
|
329
409
|
# The variable bound to a `*rest` parameter is the *Array* of rest-positional arguments,
|
|
330
410
|
# not a single element. Likewise `**kw` is bound to a `Hash[Symbol, V]`. Wrap the
|
|
331
411
|
# translated element/value type accordingly so `rest` reads as `Array[Integer]` rather
|
|
@@ -41,8 +41,8 @@ module Rigor
|
|
|
41
41
|
any = arguments.empty?
|
|
42
42
|
arguments.each do |arg|
|
|
43
43
|
if arg.is_a?(Prism::DefNode)
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
ranges << [arg.location.start_offset, arg.location.end_offset, arg.name,
|
|
45
|
+
*ModuleFunctionState.def_row_side(arg, qualified_prefix, in_singleton_class, owner)]
|
|
46
46
|
elsif (name = Source::Literals.symbol_or_string(arg))
|
|
47
47
|
ranges << [*call_span, name, :singleton, owner]
|
|
48
48
|
else
|
|
@@ -54,6 +54,17 @@ module Rigor
|
|
|
54
54
|
module ModuleFunctionState
|
|
55
55
|
module_function
|
|
56
56
|
|
|
57
|
+
# The `[kind, owner]` of a deferred-range row for a `def` passed to a `module_function` spelling: `:singleton`
|
|
58
|
+
# for a singleton def, `:both` for an instance one, and `[nil, nil]` for a def on another object's singleton
|
|
59
|
+
# (#3155), which claims no owner or side.
|
|
60
|
+
def def_row_side(def_node, qualified_prefix, in_singleton_class, owner)
|
|
61
|
+
case ScopeIndexer.def_side(def_node, qualified_prefix, in_singleton_class)
|
|
62
|
+
when :singleton then [:singleton, owner]
|
|
63
|
+
when :instance then [:both, owner]
|
|
64
|
+
else [nil, nil]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
57
68
|
# A receiverless `module_function` call, bare or named.
|
|
58
69
|
def call?(node)
|
|
59
70
|
node.name == :module_function && node.receiver.nil?
|
|
@@ -360,8 +371,8 @@ module Rigor
|
|
|
360
371
|
owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
|
|
361
372
|
node.arguments&.arguments&.each do |arg|
|
|
362
373
|
if arg.is_a?(Prism::DefNode)
|
|
363
|
-
|
|
364
|
-
|
|
374
|
+
ranges << [arg.location.start_offset, arg.location.end_offset, arg.name,
|
|
375
|
+
*ModuleFunctionState.def_row_side(arg, qualified_prefix, in_singleton_class, owner)]
|
|
365
376
|
elsif (name = symbol_argument_name(arg))
|
|
366
377
|
ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
|
|
367
378
|
else
|
|
@@ -302,7 +302,7 @@ module Rigor
|
|
|
302
302
|
# and a per-slot `T | nil` fires `call.possible-nil-receiver` on the guarded read.
|
|
303
303
|
def join_member_bindings(types, soften_slots)
|
|
304
304
|
untyped = Type::Combinator.untyped
|
|
305
|
-
return [untyped, false] if types.any? { |type| type
|
|
305
|
+
return [untyped, false] if types.any? { |type| Type::Combinator.untyped?(type) }
|
|
306
306
|
|
|
307
307
|
firm = types.reject { |type| nil_literal?(type) }
|
|
308
308
|
return [Type::Combinator.union(*types), false] if !soften_slots || firm.empty? || firm.size == types.size
|