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
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../type"
|
|
4
4
|
require_relative "external_ancestor_resolution"
|
|
5
|
-
require_relative "../
|
|
5
|
+
require_relative "../typing_state"
|
|
6
|
+
require_relative "../memo_audit"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Inference
|
|
@@ -20,8 +21,14 @@ module Rigor
|
|
|
20
21
|
NO_TARGETS = [].freeze
|
|
21
22
|
private_constant :NO_TARGETS
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
# ADR-129 WD1 — a run-scoped Memo keyed on the identity of the discovery index and the environment, the only
|
|
25
|
+
# inputs the buckets' answers read.
|
|
26
|
+
MEMO = TypingState.memo(
|
|
27
|
+
:project_method_ownership,
|
|
28
|
+
run_scoped: true, audit: ["project method defines", "block runs", "ancestry non-escaping"],
|
|
29
|
+
reason: "answer buckets keyed on the discovery index and environment identities"
|
|
30
|
+
)
|
|
31
|
+
private_constant :MEMO
|
|
25
32
|
|
|
26
33
|
module_function
|
|
27
34
|
|
|
@@ -62,9 +69,15 @@ module Rigor
|
|
|
62
69
|
return false if scope.nil?
|
|
63
70
|
|
|
64
71
|
by_method = (memo(scope)[kind][class_name] ||= {})
|
|
65
|
-
|
|
72
|
+
if by_method.key?(method_sym)
|
|
73
|
+
return MemoAudit.check("project method defines", by_method[method_sym], class_name, method_sym) do
|
|
74
|
+
compute_defines?(class_name, method_sym, kind, scope)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
66
77
|
|
|
67
|
-
by_method[method_sym] =
|
|
78
|
+
by_method[method_sym] = MemoAudit.key_audit("project method defines") do
|
|
79
|
+
compute_defines?(class_name, method_sym, kind, scope)
|
|
80
|
+
end
|
|
68
81
|
end
|
|
69
82
|
|
|
70
83
|
# One slot per thread, keyed on the identity of the scope's frozen discovery index and its environment —
|
|
@@ -72,17 +85,19 @@ module Rigor
|
|
|
72
85
|
# changes: the shape of `ExpressionTyper#class_graph_buckets`, whose rationale applies unchanged. A
|
|
73
86
|
# `Scope` merges each file's discovery with the project pre-pass, so the slot turns over per file, and
|
|
74
87
|
# an ADR-46 dependency the first lookup recorded belongs to the file every later hit serves. Pool
|
|
75
|
-
# workers are separate processes, and a Ractor has its own
|
|
88
|
+
# workers are separate processes, and a Ractor has its own {TypingState}. The `:ancestry` bucket
|
|
76
89
|
# belongs to {ClosureEscapeAnalyzer}'s ancestry step. The `:block_runs` bucket belongs to
|
|
77
90
|
# {BlockRunCount}.
|
|
78
91
|
def memo(scope)
|
|
92
|
+
return new_buckets if MemoAudit.enabled? && MemoAudit.suspended?
|
|
93
|
+
|
|
79
94
|
discovery = scope.discovery
|
|
80
95
|
environment = scope.environment
|
|
81
|
-
|
|
96
|
+
state = TypingState.current
|
|
97
|
+
slot = state[MEMO]
|
|
82
98
|
unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Thread.current[MEMO_KEY] = slot
|
|
99
|
+
slot = [discovery, environment, new_buckets]
|
|
100
|
+
state[MEMO] = slot
|
|
86
101
|
end
|
|
87
102
|
slot[2]
|
|
88
103
|
end
|
|
@@ -90,6 +105,9 @@ module Rigor
|
|
|
90
105
|
class << self
|
|
91
106
|
private
|
|
92
107
|
|
|
108
|
+
# Empty buckets: a new slot's, and a throwaway set while an audit recomputation bypasses the memo.
|
|
109
|
+
def new_buckets = { instance: {}, singleton: {}, ancestry: {}, block_runs: {}.compare_by_identity }
|
|
110
|
+
|
|
93
111
|
def class_target(class_name, kind)
|
|
94
112
|
class_name.nil? ? nil : [[class_name.to_s, kind]]
|
|
95
113
|
end
|
|
@@ -36,6 +36,9 @@ module Rigor
|
|
|
36
36
|
# `farther` is set only on an entry {#lookup_through_ancestors} returns: the other definers of the name farther
|
|
37
37
|
# on the walk (entries, or {SOURCE_DEFINER} for a project-source `def`), which {#callable?} reads before it
|
|
38
38
|
# refuses the call.
|
|
39
|
+
#
|
|
40
|
+
# `kind` stays truthful on a copy {#singleton_copy_entry} returns: the `module_function` singleton copy is a
|
|
41
|
+
# `:singleton` method even though the body it copied is an instance `def`.
|
|
39
42
|
Entry = Data.define(:class_name, :method_name, :kind, :source_path, :source_line, :return_type, :alias_of,
|
|
40
43
|
:visibility, :farther) do
|
|
41
44
|
def initialize(class_name:, method_name:, kind:, source_path:, source_line:, return_type: nil, alias_of: nil,
|
|
@@ -49,7 +52,7 @@ module Rigor
|
|
|
49
52
|
# written, with no lexical resolution, as a patch's class name is.
|
|
50
53
|
Mixin = Data.define(:owner, :module_name, :kind)
|
|
51
54
|
|
|
52
|
-
attr_reader :by_key, :write_census, :mixins, :overrides
|
|
55
|
+
attr_reader :by_key, :write_census, :mixins, :overrides, :singleton_copies
|
|
53
56
|
|
|
54
57
|
# Each owner's prepended and included modules, the most recent first.
|
|
55
58
|
def self.mixins_by_owner(mixins)
|
|
@@ -69,7 +72,12 @@ module Rigor
|
|
|
69
72
|
# @param overrides — issue #1708: `[class_name, method_name, kind] => visibility` for a named visibility call
|
|
70
73
|
# (`public :kp` in `class Integer`) on a method the class inherits rather than binds, which Ruby copies onto
|
|
71
74
|
# the class with that visibility.
|
|
72
|
-
|
|
75
|
+
# @param singleton_copies — issue #2108: `[class_name, method_name]` pairs a `module_function :x` wrote for a
|
|
76
|
+
# name the class inherits rather than binds — Ruby's public singleton copy of the instance method the name
|
|
77
|
+
# resolves to. {#lookup_through_ancestors} answers a singleton-side call for one through
|
|
78
|
+
# {#singleton_copy_entry}.
|
|
79
|
+
def initialize(entries: [], write_census: GlobalWriteCensus::EMPTY, mixins: [], overrides: {},
|
|
80
|
+
singleton_copies: [])
|
|
73
81
|
@by_key = entries.each_with_object({}) do |entry, acc|
|
|
74
82
|
key = [entry.class_name, entry.method_name, entry.kind]
|
|
75
83
|
acc[key] = entry
|
|
@@ -78,6 +86,7 @@ module Rigor
|
|
|
78
86
|
@mixins = mixins.dup.freeze
|
|
79
87
|
@mixins_of = ProjectPatchedMethods.mixins_by_owner(@mixins)
|
|
80
88
|
@overrides = overrides.dup.freeze
|
|
89
|
+
@singleton_copies = singleton_copies.to_set.freeze
|
|
81
90
|
freeze
|
|
82
91
|
end
|
|
83
92
|
|
|
@@ -121,10 +130,18 @@ module Rigor
|
|
|
121
130
|
end
|
|
122
131
|
|
|
123
132
|
entry = @by_key[[owner, method_name, side]]
|
|
133
|
+
if entry.nil? && found.nil? && side == :singleton
|
|
134
|
+
copy = singleton_copy_entry(owner, method_name, rbs_loader, source_defines)
|
|
135
|
+
# Issue #2108 — a singleton copy is a real method on `#<Class:owner>` at this exact ancestor
|
|
136
|
+
# position: Ruby's lookup ends there, so nothing farther on the walk can answer `owner.x`.
|
|
137
|
+
# Return it now — collecting `farther` would only make {#callable?} decline the refusal Ruby
|
|
138
|
+
# makes of a private or protected copy.
|
|
139
|
+
return apply_override(copy, override) if copy
|
|
140
|
+
end
|
|
124
141
|
if found
|
|
125
142
|
farther << entry if entry
|
|
126
143
|
elsif entry
|
|
127
|
-
found =
|
|
144
|
+
found = apply_override(entry, override)
|
|
128
145
|
elsif (visibility = @overrides[[owner, method_name, side]])
|
|
129
146
|
override ||= [owner, visibility]
|
|
130
147
|
end
|
|
@@ -137,9 +154,12 @@ module Rigor
|
|
|
137
154
|
|
|
138
155
|
# Issue #1708 — whether Ruby lets a call reach `entry`. A public method answers any call; a private one only a
|
|
139
156
|
# call with no receiver or with `self` as it (`explicit` is false for those); a protected one also a call from
|
|
140
|
-
# a method whose `self` is an instance of the entry's owner
|
|
141
|
-
#
|
|
142
|
-
#
|
|
157
|
+
# a method whose `self` is an instance of the entry's owner — for a `:singleton` entry, the owner itself or a
|
|
158
|
+
# class whose singleton chain holds it (`Kernel.a1` under `class << self; protected :a1` is refused from
|
|
159
|
+
# `main`, an Object, and from another class's singleton method, and answered from Kernel's own and from a
|
|
160
|
+
# `module_function` def of Kernel's, whose self is typed as an instance of Kernel though the copy runs with
|
|
161
|
+
# self = Kernel). `self_type` is the caller's `self` (`nil` at the top level, where it is `main`, an Object);
|
|
162
|
+
# a `self` this cannot name (the default) may be one, so a protected entry answers it.
|
|
143
163
|
#
|
|
144
164
|
# It never refuses a call some other definer of the name may answer: the walk's order is an approximation
|
|
145
165
|
# (a module two owners mix in is spliced at the nearer one, where Ruby keeps the first include and skips the
|
|
@@ -152,7 +172,7 @@ module Rigor
|
|
|
152
172
|
allowed =
|
|
153
173
|
case entry.visibility
|
|
154
174
|
when :private, :module_function then false
|
|
155
|
-
when :protected then self_kind_of?(self_type, entry.class_name, rbs_loader)
|
|
175
|
+
when :protected then self_kind_of?(self_type, entry.class_name, entry.kind, rbs_loader)
|
|
156
176
|
else true
|
|
157
177
|
end
|
|
158
178
|
return true if allowed
|
|
@@ -171,18 +191,50 @@ module Rigor
|
|
|
171
191
|
|
|
172
192
|
private
|
|
173
193
|
|
|
174
|
-
# Whether `self_type` may
|
|
175
|
-
# with the patches' mixins, do not hold
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
# Whether `self_type` may reach `owner`'s `entry_kind` method: false only when the ancestors of a class RBS
|
|
195
|
+
# knows, with the patches' mixins, do not hold `[owner, entry_kind]` — an instance of the owner for an
|
|
196
|
+
# instance method, the owner itself or a class whose singleton chain holds it for a singleton one. For a
|
|
197
|
+
# singleton entry a self whose class IS the owner answers too: a `module_function` / `extend self` def is
|
|
198
|
+
# typed `Nominal[owner]` though its singleton copy runs with self = owner, so `name == owner` may be the
|
|
199
|
+
# owner object itself (issue #3056).
|
|
200
|
+
def self_kind_of?(self_type, owner, entry_kind, rbs_loader)
|
|
201
|
+
name, side =
|
|
178
202
|
if self_type.nil? then ["Object", :instance]
|
|
179
203
|
elsif self_type.respond_to?(:class_name) && self_type.class_name
|
|
180
204
|
[self_type.class_name.to_s, self_type.is_a?(Type::Singleton) ? :singleton : :instance]
|
|
181
205
|
end
|
|
182
206
|
return true if name.nil? || rbs_loader.nil?
|
|
183
207
|
|
|
184
|
-
keys = ancestor_keys(name,
|
|
185
|
-
keys == [[name,
|
|
208
|
+
keys = ancestor_keys(name, side, rbs_loader)
|
|
209
|
+
keys == [[name, side]] || keys.include?([owner, entry_kind]) ||
|
|
210
|
+
(entry_kind == :singleton && side == :instance && name == owner)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# `entry` as the walk's recorded `override` ([owner, visibility], from a nearer row's named
|
|
214
|
+
# visibility call) leaves it: the override's owner as `class_name` and its visibility — or `entry`
|
|
215
|
+
# itself when the walk passed no override on the way.
|
|
216
|
+
def apply_override(entry, override)
|
|
217
|
+
override ? entry.with(class_name: override.first, visibility: override.last) : entry
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Issue #2108 — the entry a `module_function :x` on a name `owner` inherits answers `owner.x` with. Ruby
|
|
221
|
+
# copies the instance method the name resolves to onto the owner's singleton as a public method, so the
|
|
222
|
+
# copy is the entry this walk finds for the name on the owner's INSTANCE ancestors, re-attributed to the
|
|
223
|
+
# owner, re-kinded `:singleton` and made public — unless a later named visibility call (`private_class_method
|
|
224
|
+
# :x`, or `private :x` in `class << self`) recorded the pair's singleton-side override, which Ruby applies to
|
|
225
|
+
# the copy. The instance walk's `farther` definers are dropped: the copy is a real method on
|
|
226
|
+
# `#<Class:owner>` that ends the singleton-side lookup, so no instance-chain definer can answer `owner.x`,
|
|
227
|
+
# and keeping them would only make {#callable?} decline a refusal Ruby makes. nil when no copy is
|
|
228
|
+
# recorded for `[owner, method_name]`, or when the instance walk finds no patch entry — an RBS or
|
|
229
|
+
# project-source method the registry does not hold, where the singleton walk then answers whatever else
|
|
230
|
+
# it reaches, as before.
|
|
231
|
+
def singleton_copy_entry(owner, method_name, rbs_loader, source_defines)
|
|
232
|
+
return nil unless @singleton_copies.include?([owner, method_name])
|
|
233
|
+
|
|
234
|
+
copy = lookup_through_ancestors(class_name: owner, method_name: method_name, kind: :instance,
|
|
235
|
+
rbs_loader: rbs_loader, source_defines: source_defines)
|
|
236
|
+
copy&.with(class_name: owner, kind: :singleton, farther: nil,
|
|
237
|
+
visibility: @overrides[[owner, method_name, :singleton]] || :public)
|
|
186
238
|
end
|
|
187
239
|
|
|
188
240
|
def ancestor_keys(class_name, kind, rbs_loader)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
+
require_relative "../typing_state"
|
|
5
6
|
require_relative "project_patched_methods"
|
|
6
7
|
require_relative "project_patched_definedness_guard"
|
|
7
8
|
require_relative "project_patched_visibility"
|
|
@@ -45,6 +46,38 @@ module Rigor
|
|
|
45
46
|
end
|
|
46
47
|
private_constant :GuardedDef
|
|
47
48
|
|
|
49
|
+
# A `module_function` singleton copy a `def`-form produced (`module_function def x`, or a `def` under a
|
|
50
|
+
# bare `module_function`): `entry` is the copy to bind and `def_entry` the instance entry it was copied
|
|
51
|
+
# from, so {resolve_aliases} records which def the singleton binding answers — a later named
|
|
52
|
+
# `module_function :x` keeps the copy only while that def is proved unchanged (issue #3056).
|
|
53
|
+
SingletonCopy = Data.define(:entry, :def_entry) do
|
|
54
|
+
def source_path = entry.source_path
|
|
55
|
+
end
|
|
56
|
+
private_constant :SingletonCopy
|
|
57
|
+
|
|
58
|
+
# A statement the walk does not read that may rebind a method: any call other than a visibility call, a
|
|
59
|
+
# literal-named `alias_method`, or a receiverless `include` / `extend`, plus an `alias` with a computed
|
|
60
|
+
# name and an `undef`. `define_method(:x)`, `class_eval "def x"`, `send(:define_method, ...)` and
|
|
61
|
+
# `attr_reader :x` all make Ruby's next `module_function :x` copy a fresh public body, so after one
|
|
62
|
+
# {resolve_aliases} no longer assumes a copy is unchanged (issue #3056).
|
|
63
|
+
Unread = Data.define(:source_path)
|
|
64
|
+
private_constant :Unread
|
|
65
|
+
|
|
66
|
+
# The receiverless calls the walk reads as not rebinding a method of the body's own class.
|
|
67
|
+
MIXIN_CALLS = %i[include extend].freeze
|
|
68
|
+
private_constant :MIXIN_CALLS
|
|
69
|
+
|
|
70
|
+
# ADR-129 WD1 — the two per-file inputs of the walk, each a Frame {.walk_file} installs for the file's walk and
|
|
71
|
+
# restores on the way out: the `def`s on the undefined side of a definedness guard, and each `def`'s visibility.
|
|
72
|
+
# Both are frozen once built and read only by {.walk_node}'s callees.
|
|
73
|
+
#
|
|
74
|
+
# G1′: the walk is a Prism traversal that never types, so no `type_of` runs while either is installed.
|
|
75
|
+
GUARDED_DEFS = TypingState.frame(:patched_scanner_guarded_defs, reason: "installed per scanned file's walk",
|
|
76
|
+
typing_unreachable: true)
|
|
77
|
+
DEF_VISIBILITIES = TypingState.frame(:patched_scanner_def_visibilities, reason: "installed per scanned file",
|
|
78
|
+
typing_unreachable: true)
|
|
79
|
+
private_constant :GUARDED_DEFS, :DEF_VISIBILITIES
|
|
80
|
+
|
|
48
81
|
module_function
|
|
49
82
|
|
|
50
83
|
# @param paths — absolute paths to the pre-eval files. The runner has already
|
|
@@ -61,11 +94,13 @@ module Rigor
|
|
|
61
94
|
paths.each { |path| scan_file(path, collected, diagnostics, buffer, census) }
|
|
62
95
|
pending_mixins, collected = collected.partition { |item| item.is_a?(Mixins::Pending) }
|
|
63
96
|
overrides = {}
|
|
64
|
-
|
|
97
|
+
singleton_copies = Set.new
|
|
98
|
+
entries = resolve_aliases(collected, overrides, singleton_copies)
|
|
65
99
|
diagnostics.concat(duplicate_declaration_diagnostics(entries))
|
|
66
100
|
Result.new(
|
|
67
101
|
registry: ProjectPatchedMethods.new(entries: entries, write_census: census.freeze, overrides: overrides,
|
|
68
|
-
mixins: Mixins.resolve(pending_mixins, entries)
|
|
102
|
+
mixins: Mixins.resolve(pending_mixins, entries),
|
|
103
|
+
singleton_copies: singleton_copies),
|
|
69
104
|
diagnostics: diagnostics
|
|
70
105
|
)
|
|
71
106
|
end
|
|
@@ -114,23 +149,37 @@ module Rigor
|
|
|
114
149
|
# A {Visibility::Pending} (issue #1708) changes the visibility of the binding its name has when it runs, in the
|
|
115
150
|
# file that bound it, so it is not a second declaration; a name no earlier patch binds on the class is one it
|
|
116
151
|
# inherits, recorded in `overrides` ({Visibility.apply}). `module_function :x` also binds the public singleton
|
|
117
|
-
# copy.
|
|
118
|
-
|
|
152
|
+
# copy: an entry {.applied} yields for a bound name, or a `[class_name, name]` pair in `singleton_copies`
|
|
153
|
+
# (issue #2108) for an inherited one, whose body the scan cannot name.
|
|
154
|
+
#
|
|
155
|
+
# `bound_defs` holds each key's def / alias-produced entry — the def identity a visibility change does not
|
|
156
|
+
# move — and `copy_sources` each singleton key's `module_function` copy source, the pair
|
|
157
|
+
# {Visibility.apply} reads to keep a repeated `module_function :x` from re-publicing a copy
|
|
158
|
+
# `private_class_method` made private (issue #3056).
|
|
159
|
+
def resolve_aliases(collected, overrides = {}, singleton_copies = Set.new)
|
|
119
160
|
bound = {}
|
|
120
161
|
homes = {}
|
|
162
|
+
bound_defs = {}
|
|
163
|
+
copy_sources = {}
|
|
121
164
|
per_file = collected.group_by(&:source_path).values.map do |items|
|
|
122
165
|
final = {}
|
|
123
166
|
items.each do |item|
|
|
124
167
|
if item.is_a?(Visibility::Pending)
|
|
125
|
-
next Visibility.apply(item, bound, homes, overrides
|
|
168
|
+
next Visibility.apply(item, bound, homes, overrides, singleton_copies, bound_defs, copy_sources) do |copy|
|
|
169
|
+
bind(copy, bound, homes, final)
|
|
170
|
+
end
|
|
126
171
|
end
|
|
172
|
+
next if copy_provenance_item?(item, bound, homes, final, copy_sources)
|
|
127
173
|
|
|
128
174
|
entry = item.is_a?(PendingAlias) ? alias_entry(item, bound) : item
|
|
129
175
|
entry = entry.entry if entry.is_a?(GuardedDef)
|
|
130
176
|
key = [entry.class_name, entry.method_name, entry.kind]
|
|
131
|
-
|
|
177
|
+
# A guarded def dropped here may still run (`method_defined?` ignores a private method), so the
|
|
178
|
+
# copy it would rebind is no longer known to be unchanged.
|
|
179
|
+
next copy_sources.delete([key[0], key[1], :singleton]) if item.is_a?(GuardedDef) && bound.key?(key)
|
|
132
180
|
|
|
133
181
|
bind(entry, bound, homes, final)
|
|
182
|
+
record_binding(key, entry, bound_defs, copy_sources)
|
|
134
183
|
end
|
|
135
184
|
final
|
|
136
185
|
end
|
|
@@ -138,6 +187,33 @@ module Rigor
|
|
|
138
187
|
end
|
|
139
188
|
private_class_method :resolve_aliases
|
|
140
189
|
|
|
190
|
+
# The bookkeeping {resolve_aliases} keeps beside `bound`: `bound_defs` holds the def / alias
|
|
191
|
+
# entry `key` binds — the def identity a visibility change does not move, which
|
|
192
|
+
# {Visibility.apply}'s `module_function` re-copy check compares — and a singleton-side binding
|
|
193
|
+
# clears the copy provenance, since a `def self.x` between two `module_function :x` calls makes
|
|
194
|
+
# the next copy fresh (issue #3056).
|
|
195
|
+
def record_binding(key, entry, bound_defs, copy_sources)
|
|
196
|
+
bound_defs[key] = entry
|
|
197
|
+
copy_sources.delete([entry.class_name, entry.method_name, :singleton]) if entry.kind == :singleton
|
|
198
|
+
end
|
|
199
|
+
private_class_method :record_binding
|
|
200
|
+
|
|
201
|
+
# The two items that only move the `module_function` copy provenance {Visibility.apply}'s re-copy
|
|
202
|
+
# check compares (issue #3056), handled here and answered true: a {SingletonCopy} binds the copy and
|
|
203
|
+
# records which instance def entry it was made from, and an {Unread} statement forgets every record,
|
|
204
|
+
# since it may have rebound any name.
|
|
205
|
+
def copy_provenance_item?(item, bound, homes, final, copy_sources)
|
|
206
|
+
case item
|
|
207
|
+
when SingletonCopy
|
|
208
|
+
bind(item.entry, bound, homes, final)
|
|
209
|
+
copy_sources[[item.entry.class_name, item.entry.method_name, :singleton]] = item.def_entry
|
|
210
|
+
when Unread then copy_sources.clear
|
|
211
|
+
else return false
|
|
212
|
+
end
|
|
213
|
+
true
|
|
214
|
+
end
|
|
215
|
+
private_class_method :copy_provenance_item?
|
|
216
|
+
|
|
141
217
|
def bind(entry, bound, homes, final)
|
|
142
218
|
key = [entry.class_name, entry.method_name, entry.kind]
|
|
143
219
|
bound[key] = entry
|
|
@@ -184,18 +260,16 @@ module Rigor
|
|
|
184
260
|
"#{e.class}: #{e.message}. Pre-evaluation skipped for this file; " \
|
|
185
261
|
"the rest of the run proceeds."
|
|
186
262
|
)
|
|
187
|
-
ensure
|
|
188
|
-
Thread.current[:rigor_guarded_defs] = nil
|
|
189
|
-
Thread.current[:rigor_def_visibilities] = nil
|
|
190
263
|
end
|
|
191
264
|
private_class_method :scan_file
|
|
192
265
|
|
|
193
266
|
# Walks one parsed file with its definedness guards and `def` visibilities (issue #1708) in reach of
|
|
194
267
|
# {record_def_node}.
|
|
195
268
|
def walk_file(root, path, entries)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
269
|
+
TypingState.current.with_pair(GUARDED_DEFS, DefinednessGuard.defs(root).freeze,
|
|
270
|
+
DEF_VISIBILITIES, Visibility.defs(root).freeze) do
|
|
271
|
+
walk_node(root, [], false, path, entries)
|
|
272
|
+
end
|
|
199
273
|
end
|
|
200
274
|
private_class_method :walk_file
|
|
201
275
|
|
|
@@ -231,17 +305,10 @@ module Rigor
|
|
|
231
305
|
descend_singleton_class(node, qualified_prefix, source_path, entries)
|
|
232
306
|
when Prism::DefNode
|
|
233
307
|
record_def_node(node, qualified_prefix, in_singleton_class, source_path, entries)
|
|
234
|
-
when Prism::AliasMethodNode
|
|
235
|
-
|
|
236
|
-
source_path, entries)
|
|
308
|
+
when Prism::AliasMethodNode, Prism::UndefNode
|
|
309
|
+
record_keyword(node, qualified_prefix, in_singleton_class, source_path, entries)
|
|
237
310
|
when Prism::CallNode
|
|
238
|
-
|
|
239
|
-
in_singleton_class, source_path, entries)
|
|
240
|
-
if Thread.current[:rigor_def_visibilities]&.[](node).equal?(Visibility::STATEMENT)
|
|
241
|
-
pending = Visibility.pending(node, qualified_prefix, in_singleton_class, source_path)
|
|
242
|
-
entries << pending if pending
|
|
243
|
-
entries.concat(Mixins.pending(node, qualified_prefix, in_singleton_class))
|
|
244
|
-
end
|
|
311
|
+
record_call(node, qualified_prefix, in_singleton_class, source_path, entries)
|
|
245
312
|
walk_children(node, qualified_prefix, in_singleton_class, source_path, entries)
|
|
246
313
|
else
|
|
247
314
|
walk_children(node, qualified_prefix, in_singleton_class, source_path, entries)
|
|
@@ -249,6 +316,34 @@ module Rigor
|
|
|
249
316
|
end
|
|
250
317
|
private_class_method :walk_node
|
|
251
318
|
|
|
319
|
+
# An `alias` with literal names binds as {record_alias} reads it; a computed-name `alias` or an `undef`
|
|
320
|
+
# is {Unread}.
|
|
321
|
+
def record_keyword(node, qualified_prefix, in_singleton_class, source_path, entries)
|
|
322
|
+
names = node.is_a?(Prism::AliasMethodNode) ? Source::AliasNames.keyword_names(node) : nil
|
|
323
|
+
entries << Unread.new(source_path: source_path) if names.nil?
|
|
324
|
+
record_alias(node, names, qualified_prefix, in_singleton_class, source_path, entries)
|
|
325
|
+
end
|
|
326
|
+
private_class_method :record_keyword
|
|
327
|
+
|
|
328
|
+
def record_call(node, qualified_prefix, in_singleton_class, source_path, entries)
|
|
329
|
+
names = Source::AliasNames.alias_method_call_names(node)
|
|
330
|
+
entries << Unread.new(source_path: source_path) if names.nil? && unread_call?(node)
|
|
331
|
+
record_alias(node, names, qualified_prefix, in_singleton_class, source_path, entries)
|
|
332
|
+
return unless TypingState.current[DEF_VISIBILITIES]&.[](node).equal?(Visibility::STATEMENT)
|
|
333
|
+
|
|
334
|
+
pending = Visibility.pending(node, qualified_prefix, in_singleton_class, source_path)
|
|
335
|
+
entries << pending if pending
|
|
336
|
+
entries.concat(Mixins.pending(node, qualified_prefix, in_singleton_class))
|
|
337
|
+
end
|
|
338
|
+
private_class_method :record_call
|
|
339
|
+
|
|
340
|
+
def unread_call?(node)
|
|
341
|
+
return false if Visibility.call(node)
|
|
342
|
+
|
|
343
|
+
!(MIXIN_CALLS.include?(node.name) && (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)))
|
|
344
|
+
end
|
|
345
|
+
private_class_method :unread_call?
|
|
346
|
+
|
|
252
347
|
def walk_children(node, qualified_prefix, in_singleton_class, source_path, entries)
|
|
253
348
|
node.rigor_each_child do |child|
|
|
254
349
|
walk_node(child, qualified_prefix, in_singleton_class, source_path, entries)
|
|
@@ -288,9 +383,9 @@ module Rigor
|
|
|
288
383
|
source_path: source_path, source_line: line,
|
|
289
384
|
return_type: return_type
|
|
290
385
|
)
|
|
291
|
-
entry, *copies = Visibility.applied(entry,
|
|
292
|
-
entries << (
|
|
293
|
-
entries.concat(copies)
|
|
386
|
+
entry, *copies = Visibility.applied(entry, TypingState.current[DEF_VISIBILITIES]&.[](node) || :public)
|
|
387
|
+
entries << (TypingState.current[GUARDED_DEFS]&.key?(node) ? GuardedDef.new(entry: entry) : entry)
|
|
388
|
+
entries.concat(copies.map { |copy| SingletonCopy.new(entry: copy, def_entry: entry) })
|
|
294
389
|
end
|
|
295
390
|
private_class_method :record_def_node
|
|
296
391
|
|
|
@@ -98,20 +98,56 @@ module Rigor
|
|
|
98
98
|
# second declaration. A name the class does not bind is one it inherits (`public :kp` in `class Integer` over
|
|
99
99
|
# `module Kernel; private def kp`), which Ruby copies onto the class with the new visibility: `overrides` gets
|
|
100
100
|
# `key => visibility` for {ProjectPatchedMethods#lookup_through_ancestors} to apply to the body it finds farther
|
|
101
|
-
# on.
|
|
101
|
+
# on. `module_function :x` also binds the public singleton copy: for a name the class binds, {.applied} yields
|
|
102
|
+
# it to bind here; for an inherited one (issue #2108) `singleton_copies` records `[class_name, name]`, which
|
|
103
|
+
# {ProjectPatchedMethods#singleton_copy_entry} resolves through the owner's instance ancestors at call time
|
|
104
|
+
# (an approximation: Ruby snapshots the body when module_function runs)
|
|
105
|
+
# — the copy's body is whichever method the name reaches there, which the scan cannot name.
|
|
106
|
+
# Yields each copy that binds (`module_function :x`'s singleton), at the call's line, for the caller to
|
|
102
107
|
# bind.
|
|
103
|
-
|
|
108
|
+
#
|
|
109
|
+
# `bound_defs` maps each bound key to the entry a `def` / `alias` last produced for it — the def
|
|
110
|
+
# identity a visibility change (`private :x`) does not move — and `copy_sources` maps a singleton key
|
|
111
|
+
# to the instance def entry its `module_function` copy was made from. Ruby re-copies the instance
|
|
112
|
+
# method onto the singleton only when that def changed since the last copy (issue #3056): a
|
|
113
|
+
# `private_class_method :x` between two `module_function :x` calls keeps the copy private, while a
|
|
114
|
+
# `def x` redefinition, or any statement the scan does not read, makes the next copy public again.
|
|
115
|
+
def apply(pending, bound, homes, overrides, singleton_copies, bound_defs, copy_sources)
|
|
104
116
|
pending.names.each do |name|
|
|
105
117
|
key = [pending.class_name, name, pending.kind]
|
|
106
118
|
entry = bound[key]
|
|
107
|
-
|
|
119
|
+
if entry.nil?
|
|
120
|
+
overrides[key] = pending.visibility
|
|
121
|
+
singleton_copies << [pending.class_name, name] if pending.visibility == :module_function
|
|
122
|
+
next
|
|
123
|
+
end
|
|
108
124
|
|
|
109
125
|
changed, *copies = applied(entry, pending.visibility)
|
|
126
|
+
copies = [] if unchanged_module_function_copy?(pending, key, bound_defs, copy_sources)
|
|
110
127
|
bound[key] = homes[key][key] = changed
|
|
111
128
|
copies.each { |copy| yield copy.with(source_path: pending.source_path, source_line: pending.source_line) }
|
|
112
129
|
end
|
|
113
130
|
end
|
|
114
131
|
|
|
132
|
+
# Issue #3056 — true only when `pending` is a `module_function` named call whose singleton copy is
|
|
133
|
+
# proved to answer the same instance def: the copy's recorded source is still the def / alias
|
|
134
|
+
# binding `key` has. `resolve_aliases` drops that record whenever something may have rebound the
|
|
135
|
+
# name (a singleton-side `def`, a guarded def it dropped, or any statement the walk does not read),
|
|
136
|
+
# and then Ruby's re-copy is public, the reading that refuses no call Ruby runs. `bound_defs[key]` is
|
|
137
|
+
# the def entry `key` binds — a visibility change does not move it — and `copy_sources[skey]` the
|
|
138
|
+
# instance def entry the singleton's current copy was made from; either way the source is
|
|
139
|
+
# (re)recorded for the next call.
|
|
140
|
+
def unchanged_module_function_copy?(pending, key, bound_defs, copy_sources)
|
|
141
|
+
return false unless pending.visibility == :module_function
|
|
142
|
+
|
|
143
|
+
skey = [key[0], key[1], :singleton]
|
|
144
|
+
def_entry = bound_defs[key]
|
|
145
|
+
source = copy_sources[skey]
|
|
146
|
+
copy_sources[skey] = def_entry
|
|
147
|
+
!source.nil? && source.equal?(def_entry)
|
|
148
|
+
end
|
|
149
|
+
private_class_method :unchanged_module_function_copy?
|
|
150
|
+
|
|
115
151
|
# `entry` as `visibility` leaves it, then any copy that binds: `module_function` makes an instance method
|
|
116
152
|
# private and binds a public singleton copy, as Ruby does.
|
|
117
153
|
def applied(entry, visibility)
|