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,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
-
require_relative "../
|
|
5
|
+
require_relative "../typing_state"
|
|
6
|
+
require_relative "../source/node_children"
|
|
6
7
|
require_relative "closure_escape_analyzer"
|
|
7
8
|
require_relative "construction_escape"
|
|
9
|
+
require_relative "../memo_audit"
|
|
8
10
|
|
|
9
11
|
module Rigor
|
|
10
12
|
module Inference
|
|
@@ -55,11 +57,17 @@ module Rigor
|
|
|
55
57
|
Prism::LocalVariableAndWriteNode
|
|
56
58
|
].freeze
|
|
57
59
|
ALL = :all
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
NO_ARGUMENTS = [].freeze
|
|
61
|
+
# ADR-129 WD1 — a run-scoped Memo, emptied when a run ends so no verdict outlives the body it read: keyed by
|
|
62
|
+
# body node, and the unsafe names are a pure function of the body and its frame's parameters.
|
|
63
|
+
UNSAFE_CACHE = TypingState.memo(
|
|
64
|
+
:remover_narrowing_unsafe,
|
|
65
|
+
run_scoped: true, audit: "remover narrowing unsafe",
|
|
66
|
+
reason: "keyed by the body node; the unsafe local names are a pure function of the body"
|
|
67
|
+
)
|
|
60
68
|
private_constant :REFLECTIVE_CALLS, :SELF_CONVERSIONS, :PURE_READERS, :IMMEDIATE_BLOCK_CALLS, :FRAME_CALLS,
|
|
61
69
|
:ANY_RECEIVER_FRAME_CALLS, :NON_WRITER_OPERATORS, :CORE_COLLECTIONS,
|
|
62
|
-
:LOCAL_REFERENCES, :LOCAL_WRITES, :ALL, :
|
|
70
|
+
:LOCAL_REFERENCES, :LOCAL_WRITES, :ALL, :NO_ARGUMENTS, :UNSAFE_CACHE
|
|
63
71
|
|
|
64
72
|
module_function
|
|
65
73
|
|
|
@@ -70,30 +78,54 @@ module Rigor
|
|
|
70
78
|
body = frame&.body
|
|
71
79
|
return true if body.nil?
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
names = table[body] ||= unsafe_names(body, frame.parameters)
|
|
81
|
+
names = memoised_unsafe_names(body, frame.parameters)
|
|
75
82
|
names.equal?(ALL) || names.include?(name.to_sym)
|
|
76
83
|
end
|
|
77
84
|
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
# {.unsafe_names} of `body`, memoised for the run.
|
|
86
|
+
def memoised_unsafe_names(body, parameters)
|
|
87
|
+
return unsafe_names(body, parameters) if MemoAudit.enabled? && MemoAudit.suspended?
|
|
88
|
+
|
|
89
|
+
table = TypingState.current.weak_key_map(UNSAFE_CACHE)
|
|
90
|
+
stored = table[body]
|
|
91
|
+
if stored.nil?
|
|
92
|
+
return table[body] = MemoAudit.key_audit("remover narrowing unsafe") { unsafe_names(body, parameters) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
MemoAudit.check("remover narrowing unsafe", stored, body) { unsafe_names(body, parameters) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# `names` collects the locals; a collector that answers `site=` ({AliasMutationFloor}) is told, before each name
|
|
99
|
+
# it is given, the node that hands the local on, or nil when no single node does (a capture by a closure that may
|
|
100
|
+
# run later, a zero-argument `super`), reads value positions itself when it answers `value_held` ({.precise?}),
|
|
101
|
+
# and is told when the walk enters (`enter(node, parent)`) and leaves
|
|
102
|
+
# (`leave(node)`) each node. One that answers `keeps_arguments?` says which calls' plain arguments it counts as
|
|
103
|
+
# held.
|
|
104
|
+
def unsafe_names(body, parameters = nil, names = Set.new)
|
|
105
|
+
sited = names.respond_to?(:site=)
|
|
80
106
|
catch(ALL) do
|
|
81
|
-
collect_unsafe(body, false, names)
|
|
107
|
+
collect_unsafe(body, false, names, sited:)
|
|
108
|
+
names.site = nil if sited
|
|
82
109
|
parameter_names(parameters, names) if forwards_parameters?(body)
|
|
83
110
|
return names.freeze
|
|
84
111
|
end
|
|
85
112
|
ALL
|
|
86
113
|
end
|
|
87
114
|
|
|
88
|
-
def collect_unsafe(node, in_closure, names)
|
|
115
|
+
def collect_unsafe(node, in_closure, names, sited: false, parent: nil)
|
|
89
116
|
in_closure ||= node.is_a?(Prism::LambdaNode)
|
|
117
|
+
if sited
|
|
118
|
+
names.enter(node, parent)
|
|
119
|
+
names.site = in_closure ? nil : node
|
|
120
|
+
end
|
|
90
121
|
names << node.name if in_closure && LOCAL_REFERENCES.any? { |k| node.is_a?(k) }
|
|
91
122
|
record_node(node, names)
|
|
92
123
|
|
|
93
|
-
node.
|
|
124
|
+
node.rigor_each_child do |child|
|
|
94
125
|
child_in_closure = in_closure || (child.is_a?(Prism::BlockNode) && deferred_block?(node))
|
|
95
|
-
collect_unsafe(child, child_in_closure, names)
|
|
126
|
+
collect_unsafe(child, child_in_closure, names, sited:, parent: node)
|
|
96
127
|
end
|
|
128
|
+
names.leave(node) if sited
|
|
97
129
|
end
|
|
98
130
|
|
|
99
131
|
def record_node(node, names) # rubocop:disable Metrics/CyclomaticComplexity
|
|
@@ -101,7 +133,9 @@ module Rigor
|
|
|
101
133
|
when Prism::CallNode then record_call(node, names)
|
|
102
134
|
when Prism::YieldNode, Prism::SuperNode then node.arguments&.arguments&.each { |a| record_held(a, names) }
|
|
103
135
|
when Prism::ArrayNode then node.elements.each { |element| record_held(element, names) }
|
|
104
|
-
when Prism::AssocNode
|
|
136
|
+
when Prism::AssocNode
|
|
137
|
+
record_held(node.key, names)
|
|
138
|
+
record_held(node.value, names)
|
|
105
139
|
when Prism::SplatNode, Prism::BlockArgumentNode then record_held(node.expression, names)
|
|
106
140
|
when Prism::CaseMatchNode then record_pattern(node.predicate, node.conditions, names)
|
|
107
141
|
when Prism::MatchRequiredNode, Prism::MatchPredicateNode then record_pattern(node.value, [node.pattern], names)
|
|
@@ -115,27 +149,42 @@ module Rigor
|
|
|
115
149
|
|
|
116
150
|
receiver = unparenthesised(node.receiver)
|
|
117
151
|
names << receiver.name if receiver.is_a?(Prism::LocalVariableReadNode) && REFLECTIVE_CALLS.include?(node.name)
|
|
118
|
-
arguments = node.arguments&.arguments ||
|
|
152
|
+
arguments = node.arguments&.arguments || NO_ARGUMENTS
|
|
119
153
|
if writer_call?(node) && !arguments.empty?
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
every_read(value, names)
|
|
123
|
-
else
|
|
154
|
+
record_writer_call(arguments, names)
|
|
155
|
+
elsif keeps_arguments?(node, names)
|
|
124
156
|
arguments.each { |argument| record_held(argument, names) }
|
|
125
157
|
end
|
|
126
158
|
end
|
|
127
159
|
|
|
160
|
+
def record_writer_call(arguments, names)
|
|
161
|
+
*keys, value = arguments
|
|
162
|
+
keys.each { |key| record_held(key, names) }
|
|
163
|
+
precise?(names) ? names.value_held(value) : every_read(value, names)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def keeps_arguments?(node, names) = !names.respond_to?(:keeps_arguments?) || names.keeps_arguments?(node)
|
|
167
|
+
|
|
168
|
+
# A collector that reads a value position itself ({AliasMutationFloor}): `value_held(node)` for a value handed
|
|
169
|
+
# to another holder (an element, an argument, an index or attribute write's value), `value_written(node)` for a
|
|
170
|
+
# write node, and `value_bound(value, targets)` for a multiple assignment or a pattern. The default collector
|
|
171
|
+
# reads every local inside the value instead.
|
|
172
|
+
def precise?(names) = names.respond_to?(:value_held)
|
|
173
|
+
|
|
128
174
|
# A zero-argument `super` passes every parameter of the method on, implicitly. (A `...` forward passes only the
|
|
129
175
|
# anonymous parameters, which name no local.)
|
|
130
176
|
def forwards_parameters?(node)
|
|
131
|
-
node.is_a?(Prism::ForwardingSuperNode)
|
|
177
|
+
return true if node.is_a?(Prism::ForwardingSuperNode)
|
|
178
|
+
|
|
179
|
+
node.rigor_each_child { |child| return true if forwards_parameters?(child) }
|
|
180
|
+
false
|
|
132
181
|
end
|
|
133
182
|
|
|
134
183
|
def parameter_names(node, names)
|
|
135
184
|
return if node.nil?
|
|
136
185
|
|
|
137
186
|
names << node.name if node.class.name.end_with?("ParameterNode") && node.respond_to?(:name) && node.name
|
|
138
|
-
node.
|
|
187
|
+
node.rigor_each_child { |child| parameter_names(child, names) }
|
|
139
188
|
end
|
|
140
189
|
|
|
141
190
|
def frame_call?(node)
|
|
@@ -156,6 +205,7 @@ module Rigor
|
|
|
156
205
|
# A write's value hands on every local read anywhere inside it, and the local it binds holds what it read.
|
|
157
206
|
def record_write(node, names)
|
|
158
207
|
return unless node.class.name.end_with?("WriteNode") && node.respond_to?(:value)
|
|
208
|
+
return names.value_written(node) if precise?(names)
|
|
159
209
|
return unless every_read(node.value, names)
|
|
160
210
|
|
|
161
211
|
names << node.name if LOCAL_WRITES.any? { |k| node.is_a?(k) } && !fresh_value?(node.value)
|
|
@@ -178,12 +228,14 @@ module Rigor
|
|
|
178
228
|
end
|
|
179
229
|
|
|
180
230
|
def record_multi_write(node, names)
|
|
231
|
+
return names.value_bound(node.value, [*node.lefts, node.rest, *node.rights]) if precise?(names)
|
|
181
232
|
return unless every_read(node.value, names)
|
|
182
233
|
|
|
183
234
|
local_targets([*node.lefts, node.rest, *node.rights], names)
|
|
184
235
|
end
|
|
185
236
|
|
|
186
237
|
def record_pattern(subject, patterns, names)
|
|
238
|
+
return names.value_bound(subject, patterns) if precise?(names)
|
|
187
239
|
return unless every_read(subject, names)
|
|
188
240
|
|
|
189
241
|
local_targets(patterns, names)
|
|
@@ -195,7 +247,8 @@ module Rigor
|
|
|
195
247
|
|
|
196
248
|
found = node.is_a?(Prism::LocalVariableReadNode)
|
|
197
249
|
names << node.name if found
|
|
198
|
-
node.
|
|
250
|
+
node.rigor_each_child { |child| found = true if every_read(child, names) }
|
|
251
|
+
found
|
|
199
252
|
end
|
|
200
253
|
|
|
201
254
|
def local_targets(nodes, names)
|
|
@@ -208,10 +261,11 @@ module Rigor
|
|
|
208
261
|
# A held position: every local read inside it but the receiver of a {PURE_READERS} call.
|
|
209
262
|
def record_held(node, names)
|
|
210
263
|
return if node.nil?
|
|
264
|
+
return names.value_held(node) if precise?(names)
|
|
211
265
|
return if pure_read?(node)
|
|
212
266
|
|
|
213
267
|
names << node.name if node.is_a?(Prism::LocalVariableReadNode)
|
|
214
|
-
node.
|
|
268
|
+
node.rigor_each_child { |child| record_held(child, names) }
|
|
215
269
|
end
|
|
216
270
|
|
|
217
271
|
# A {PURE_READERS} call on a local hands the local on to nothing. Its own arguments are held positions of the
|
|
@@ -6,8 +6,9 @@ module Rigor
|
|
|
6
6
|
module Inference
|
|
7
7
|
module ScopeIndexer
|
|
8
8
|
# The `class_cvars` discovery table (slice 7 phase 6) as a {DeclarationWalk} collector: every
|
|
9
|
-
# `@@x = …` a `def` body writes, typed under the census scope and unioned per class
|
|
10
|
-
#
|
|
9
|
+
# `@@x = …` a `def` body writes, typed under the census scope and unioned per class, joined by what a
|
|
10
|
+
# `class` / `module` body writes to the same cvar. Ruby cvars are shared by the instance and singleton
|
|
11
|
+
# facets, so every `def` counts, `def self.x` included.
|
|
11
12
|
class ClassCvarsCollector
|
|
12
13
|
include DeclarationWalk::Collector
|
|
13
14
|
|
|
@@ -15,6 +16,14 @@ module Rigor
|
|
|
15
16
|
@table = {}
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
# Issue #3204 — a `class` / `module` body's own writes, outside its `def`s, under the body's context;
|
|
20
|
+
# each only joins an entry a `def` body's write creates. An unnameable cref (a header below
|
|
21
|
+
# `class << expr`) leaves the prefix empty, and the gather skips it.
|
|
22
|
+
def on_declaration(node, _context, body)
|
|
23
|
+
ScopeIndexer.collect_body_cvar_writes(node.body, body.prefix, body.scope, @table)
|
|
24
|
+
DeclarationWalk::DESCEND
|
|
25
|
+
end
|
|
26
|
+
|
|
18
27
|
# `@@x` in a `def` resolves through the LEXICAL cref: `Module.nesting` is the same inside a meta-new or
|
|
19
28
|
# eval block, so the context's `prefix` keys the write and its rebound `self` never does —
|
|
20
29
|
# `K = Class.new { def m = @@x }` inside `class C` writes `C::@@x`. The walk goes no further in: the
|
|
@@ -25,10 +34,8 @@ module Rigor
|
|
|
25
34
|
DeclarationWalk::DECLINE
|
|
26
35
|
end
|
|
27
36
|
|
|
28
|
-
# The table as the discovery index holds it
|
|
29
|
-
def table
|
|
30
|
-
@table.transform_values(&:freeze).freeze
|
|
31
|
-
end
|
|
37
|
+
# The table as the discovery index holds it ({ScopeIndexer#finished_cvar_census}).
|
|
38
|
+
def table = ScopeIndexer.finished_cvar_census(@table)
|
|
32
39
|
end
|
|
33
40
|
end
|
|
34
41
|
end
|
|
@@ -4,6 +4,7 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../../reflection"
|
|
6
6
|
require_relative "../method_parameter_binder"
|
|
7
|
+
require_relative "../../typing_state"
|
|
7
8
|
require_relative "../global_write_census"
|
|
8
9
|
require_relative "unseen_ivar_writers"
|
|
9
10
|
require_relative "derived_param_value"
|
|
@@ -58,27 +59,27 @@ module Rigor
|
|
|
58
59
|
module_exec].to_set.freeze
|
|
59
60
|
private_constant :OPAQUE_EVAL_CALLS
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
# ADR-129 WD1 — a Frame: the scope is installed around a census and restored on the way out.
|
|
63
|
+
WRITER_SCOPE = TypingState.frame(
|
|
64
|
+
:declared_param_writer_scope,
|
|
65
|
+
reason: "the scope whose project tables the writer gates read, installed per census",
|
|
66
|
+
# G1′: the census types its defaults under the Frame, but only the writer gates, outside `type_of`, read it.
|
|
67
|
+
typing_never_reads: true
|
|
68
|
+
)
|
|
69
|
+
private_constant :WRITER_SCOPE
|
|
63
70
|
|
|
64
71
|
module_function
|
|
65
72
|
|
|
66
73
|
# Runs the block with `scope` as the one whose project tables the unseen-writer and single-file gates read, in
|
|
67
74
|
# place of the census's own scope. `rigor sig-gen` indexes each file over the project tables less the member
|
|
68
75
|
# layouts; this scope carries all of them, so another file's writer, ancestor or reopening is seen.
|
|
69
|
-
def with_writer_scope(scope)
|
|
76
|
+
def with_writer_scope(scope, &)
|
|
70
77
|
return yield if scope.nil?
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
Thread.current[WRITER_SCOPE_KEY] = scope
|
|
74
|
-
begin
|
|
75
|
-
yield
|
|
76
|
-
ensure
|
|
77
|
-
Thread.current[WRITER_SCOPE_KEY] = previous
|
|
78
|
-
end
|
|
79
|
+
TypingState.current.with(WRITER_SCOPE, scope, &)
|
|
79
80
|
end
|
|
80
81
|
|
|
81
|
-
def writer_scope(scope) =
|
|
82
|
+
def writer_scope(scope) = TypingState.current[WRITER_SCOPE] || scope
|
|
82
83
|
|
|
83
84
|
# `{InstanceVariableWriteNode => Rigor::Type}` for `def_node`'s creditable writes, or {EMPTY}. The caller
|
|
84
85
|
# passes only an instance `def`: a singleton body's `@x` is the class object's.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../analysis/dependency_recorder"
|
|
4
|
-
require_relative "../../
|
|
4
|
+
require_relative "../../typing_state"
|
|
5
|
+
require_relative "../../memo_audit"
|
|
5
6
|
require_relative "../../type"
|
|
6
|
-
require_relative "../
|
|
7
|
+
require_relative "../census_foreign_block"
|
|
8
|
+
require_relative "../ivar_writer_census"
|
|
7
9
|
|
|
8
10
|
module Rigor
|
|
9
11
|
module Inference
|
|
@@ -13,10 +15,10 @@ module Rigor
|
|
|
13
15
|
#
|
|
14
16
|
# - the writes another file's reopening of `K` makes;
|
|
15
17
|
# - the writes of every project module and superclass on `K`'s instance resolution chain (`include`,
|
|
16
|
-
# `prepend`, transitively, and
|
|
17
|
-
# (`writes`, taken before the read-before-write `nil` is
|
|
18
|
-
# and its read-before-write adds nothing, and only for an
|
|
19
|
-
# file) and from every other file's entries for it.
|
|
18
|
+
# `prepend`, transitively, and `<`; for a module `K`, the modules it includes or prepends), from this file's
|
|
19
|
+
# own census of that ancestor where it declares one (`writes`, taken before the read-before-write `nil` is
|
|
20
|
+
# added, so an ancestor's `initialize` exempts nothing and its read-before-write adds nothing, and only for an
|
|
21
|
+
# ivar an instance body of the ancestor writes in this file) and from every other file's entries for it.
|
|
20
22
|
#
|
|
21
23
|
# Another file's write comes from its `[:own_write_type, owner, ivar, value, path]` census entry
|
|
22
24
|
# ({IvarWriterCensus}): a literal joins as its `Constant`, anything else as `Dynamic[top]`. An ivar the census
|
|
@@ -29,9 +31,23 @@ module Rigor
|
|
|
29
31
|
# incremental session fires when a file's entries for the pair appear, vanish or change value; and the chain
|
|
30
32
|
# files its class edges ({Scope::ResolutionChain#record}), so an `include` added or removed re-checks the reader.
|
|
31
33
|
module IvarContributions
|
|
32
|
-
|
|
34
|
+
# ADR-129 WD1 — a run-scoped Memo: the table is a pure function of the census it is keyed on by identity.
|
|
35
|
+
TABLE = TypingState.memo(
|
|
36
|
+
:ivar_contributions_table,
|
|
37
|
+
run_scoped: true, audit: "ivar contributions table",
|
|
38
|
+
reason: "the contribution table of one census; keyed on the census's identity"
|
|
39
|
+
)
|
|
33
40
|
EMPTY = {}.freeze
|
|
34
|
-
|
|
41
|
+
NONE = [].freeze
|
|
42
|
+
BINDINGS = :bindings
|
|
43
|
+
CANDIDATE_KEYS = :candidate_keys
|
|
44
|
+
ANCESTOR_ROOTS = CensusForeignBlock::ANCESTOR_ROOTS.to_a.freeze
|
|
45
|
+
# The one implicit root a class whose superclass chain stops at `BasicObject` keeps.
|
|
46
|
+
BASIC_ROOTS = %w[BasicObject].freeze
|
|
47
|
+
SEPARATOR = CensusForeignBlock::CANDIDATE_SEPARATOR
|
|
48
|
+
OBJECT_MARK = CensusForeignBlock::OBJECT_MARK
|
|
49
|
+
private_constant :TABLE, :EMPTY, :NONE, :BINDINGS, :CANDIDATE_KEYS, :ANCESTOR_ROOTS, :BASIC_ROOTS, :SEPARATOR,
|
|
50
|
+
:OBJECT_MARK
|
|
35
51
|
|
|
36
52
|
module_function
|
|
37
53
|
|
|
@@ -42,34 +58,148 @@ module Rigor
|
|
|
42
58
|
path = canonical(path)
|
|
43
59
|
|
|
44
60
|
table = table(scope.discovered_global_write_census)
|
|
61
|
+
resolution = nil
|
|
45
62
|
accumulator.each do |class_name, entries|
|
|
46
63
|
next unless class_name.is_a?(String) && !entries.empty?
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
resolution ||= memoized_resolution(scope, table)
|
|
66
|
+
owners = chain_owners(scope, class_name, resolution)
|
|
67
|
+
any = any_ivar_types(owners, table)
|
|
49
68
|
entries.each_key do |ivar|
|
|
50
69
|
joined = contributed(owners, class_name, ivar, table, writes, path)
|
|
70
|
+
joined = joined ? Type::Combinator.union(joined, any) : any if any
|
|
51
71
|
entries[ivar] = Type::Combinator.union(entries[ivar], joined) if joined
|
|
52
72
|
end
|
|
53
73
|
end
|
|
54
74
|
end
|
|
55
75
|
|
|
56
76
|
# `K` and the project classes and modules on its instance chain, in chain order, with their class edges filed.
|
|
57
|
-
# A module's
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
# A module's chain holds the modules it includes or prepends (#2487): each is on the chain of every class that
|
|
78
|
+
# includes the module, so its writes land on every object a read in the module's own methods sees. `extend`
|
|
79
|
+
# is no instance chain entry, for a module as for a class.
|
|
80
|
+
#
|
|
81
|
+
# Issue #2983 — the chain is read under the `:constants` flavor, so a project class or module with no method of
|
|
82
|
+
# its own (`class Anc; end`, whose writer a `class_eval` installs) is an owner too. {ANCESTOR_ROOTS}
|
|
83
|
+
# (`Object`, `BasicObject`, `Kernel`) follow, as every instance has them, except that a class whose superclass
|
|
84
|
+
# chain stops at `BasicObject` keeps `BasicObject` alone; then each owner's alias and instance constants
|
|
85
|
+
# (`[:binds_class, …]` entries), and every candidate key that resolves to an owner ({.resolution}), under which
|
|
86
|
+
# a `class_eval` rooted at that constant files its code.
|
|
87
|
+
def chain_owners(scope, class_name, resolution)
|
|
88
|
+
ancestors, reaches_object = ancestor_names(scope, class_name)
|
|
89
|
+
names = [class_name, *ancestors, *(reaches_object ? ANCESTOR_ROOTS : BASIC_ROOTS)].uniq
|
|
90
|
+
aliases, bound = resolution
|
|
91
|
+
bound_names = names.flat_map do |name|
|
|
92
|
+
segment = name.split("::").last
|
|
93
|
+
Analysis::DependencyRecorder.read_name(:binds_class, segment) if Analysis::DependencyRecorder.active?
|
|
94
|
+
bound[name] || NONE
|
|
95
|
+
end
|
|
96
|
+
(names + bound_names).uniq.flat_map { |owner| [owner, *(aliases[owner] || NONE)] }.uniq
|
|
97
|
+
end
|
|
60
98
|
|
|
61
|
-
|
|
99
|
+
# The project ancestors on `class_name`'s instance chain, and whether the chain reaches `Object`: false when a
|
|
100
|
+
# superclass edge names `BasicObject`, which has neither `Object` nor `Kernel` among its ancestors.
|
|
101
|
+
def ancestor_names(scope, class_name)
|
|
102
|
+
chain = Scope::ResolutionChain.for(scope, class_name, :instance, :constants)
|
|
62
103
|
# A guard: the class body's own `include` / `<` resolution already files these edges; kept so the read
|
|
63
104
|
# states its own dependency.
|
|
64
105
|
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
65
|
-
|
|
106
|
+
names = chain.entries.filter_map do |entry|
|
|
66
107
|
# An external entry has no name, so `filter_map` drops it. The side check is a guard: an instance chain
|
|
67
108
|
# holds only instance-side entries.
|
|
68
109
|
entry.name if entry.side == :instance && entry.name != class_name
|
|
69
110
|
end
|
|
70
|
-
[
|
|
111
|
+
[names, chain.entries.none? { |entry| basic_object_edge?(entry) }]
|
|
112
|
+
rescue StandardError
|
|
113
|
+
[NONE, true]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def basic_object_edge?(entry)
|
|
117
|
+
entry.external? && entry.superclass_edge &&
|
|
118
|
+
(entry.raw.to_s.delete_prefix("::") == "BasicObject" || entry.candidates.include?("BasicObject"))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Review round 1 of #3001 — the candidate keys ({CensusForeignBlock.candidate_key}) resolved against the
|
|
122
|
+
# project: each to its first candidate the project declares as a class or module, or binds as a constant, else
|
|
123
|
+
# to its last (top-level) spelling, as Ruby's lexical lookup finds the nearest. Answers `[aliases, bound]`:
|
|
124
|
+
# `{ resolved owner => [candidate key, ...] }` over the census's owners, and `{ resolved class => [bound
|
|
125
|
+
# constant, ...] }` over its `[:binds_class, …]` entries.
|
|
126
|
+
#
|
|
127
|
+
# Review round 2: a candidate a signature declares (`sig/`, a gem's RBS) counts as existing too, so a nearer
|
|
128
|
+
# `N::Anc` only RBS knows wins over a top-level project `Anc` and credits no project class; and a key carrying
|
|
129
|
+
# {OBJECT_MARK} joins only when it resolves to an instance binding.
|
|
130
|
+
def resolution(scope, table)
|
|
131
|
+
bindings = table[BINDINGS] || NONE
|
|
132
|
+
constants = bindings.to_set { |_, name, _| name }
|
|
133
|
+
instances = bindings.filter_map { |_, name, kind| name if kind == :instance }.to_set
|
|
134
|
+
bound = {}
|
|
135
|
+
bindings.each { |key, name, _| (bound[resolve(scope, key, constants)] ||= []) << name }
|
|
136
|
+
aliases = {}
|
|
137
|
+
(table[CANDIDATE_KEYS] || NONE).each do |key|
|
|
138
|
+
resolved = resolve(scope, key.delete_prefix(OBJECT_MARK), constants)
|
|
139
|
+
next if key.start_with?(OBJECT_MARK) && (!instances.include?(resolved) || module?(scope, resolved))
|
|
140
|
+
|
|
141
|
+
(aliases[resolved] ||= []) << key
|
|
142
|
+
end
|
|
143
|
+
[aliases, bound]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def resolve(scope, key, constants)
|
|
147
|
+
candidates = key.split(SEPARATOR)
|
|
148
|
+
candidates.find do |name|
|
|
149
|
+
constants.include?(name) || scope.discovered_classes.key?(name) ||
|
|
150
|
+
scope.discovered_superclasses.key?(name) || scope.discovered_includes.key?(name) || declared?(scope, name)
|
|
151
|
+
end || candidates.last
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Review round 3 of #3001 — {.resolution} for `table`, computed once per run: it reads only the census and the
|
|
155
|
+
# project-wide declaration tables and signatures, which no file changes within a run. The value rides the
|
|
156
|
+
# {TABLE} slot beside the table it was computed from, keyed on the discovery index it read.
|
|
157
|
+
def memoized_resolution(scope, table)
|
|
158
|
+
slot = TypingState.current[TABLE]
|
|
159
|
+
return resolution(scope, table) unless slot && slot[1].equal?(table)
|
|
160
|
+
|
|
161
|
+
discovery = scope.discovery
|
|
162
|
+
cached = slot[2]
|
|
163
|
+
return cached[1] if cached && cached[0].equal?(discovery)
|
|
164
|
+
|
|
165
|
+
value = resolution(scope, table)
|
|
166
|
+
slot[2] = [discovery, value]
|
|
167
|
+
value
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Review round 3 of #3001 — whether `name` is a class or module the project or a signature declares, whose
|
|
171
|
+
# `.class` / `.singleton_class` no instance of it runs, whatever a constructor binding says. A guard beside
|
|
172
|
+
# {CensusForeignBlock.binding_entry}'s meta-class check, which records no such binding to begin with.
|
|
173
|
+
def module?(scope, name)
|
|
174
|
+
scope.discovered_classes.key?(name) || scope.discovered_superclasses.key?(name) ||
|
|
175
|
+
scope.discovered_includes.key?(name) || scope.environment&.rbs_loader&.class_known?(name) || false
|
|
71
176
|
rescue StandardError
|
|
72
|
-
|
|
177
|
+
true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Whether a loaded signature declares `name` as a class, module or constant.
|
|
181
|
+
def declared?(scope, name)
|
|
182
|
+
loader = scope.environment&.rbs_loader
|
|
183
|
+
return false if loader.nil?
|
|
184
|
+
|
|
185
|
+
loader.class_known?(name) || !loader.constant_declaration(name).nil?
|
|
186
|
+
rescue StandardError
|
|
187
|
+
false
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Issue #2983 — what the owners' string `class_eval`s contribute to every ivar (`[:own_write_type, owner, :*,
|
|
191
|
+
# :dynamic]`), from every file, this one included: the per-file census reads no String either.
|
|
192
|
+
def any_ivar_types(owners, table)
|
|
193
|
+
types = []
|
|
194
|
+
owners.each do |owner|
|
|
195
|
+
# A guard: a String `class_eval` also gives its module the discovery index's dynamic mark, whose edge
|
|
196
|
+
# re-checks the reader when one comes or goes; kept so the read states its own dependency.
|
|
197
|
+
if Analysis::DependencyRecorder.active?
|
|
198
|
+
Analysis::DependencyRecorder.read_name(:own_write_type, "#{owner}##{IvarWriterCensus::ANY_IVAR}")
|
|
199
|
+
end
|
|
200
|
+
add_entry_types(types, table.dig(owner, IvarWriterCensus::ANY_IVAR) || EMPTY, nil)
|
|
201
|
+
end
|
|
202
|
+
types.empty? ? nil : Type::Combinator.union(*types)
|
|
73
203
|
end
|
|
74
204
|
|
|
75
205
|
# The union of what the owners contribute to `ivar`, or nil for nothing.
|
|
@@ -80,19 +210,25 @@ module Rigor
|
|
|
80
210
|
Analysis::DependencyRecorder.read_name(:own_write_type, "#{owner}##{ivar}")
|
|
81
211
|
end
|
|
82
212
|
by_path = table.dig(owner, ivar) || EMPTY
|
|
83
|
-
# This file's census of an ancestor counts only an ivar an instance body of it writes here
|
|
84
|
-
#
|
|
213
|
+
# This file's census of an ancestor counts only an ivar an instance body of it writes here. `writes` leaves
|
|
214
|
+
# out an ivar a `def self.` of the ancestor also writes (the census files that write under it, #1958, and it
|
|
215
|
+
# runs on the module object); this file's census entries for it join instead, as in another file (#2931).
|
|
85
216
|
own = writes.dig(owner, ivar) if owner != class_name && by_path.key?(path)
|
|
86
217
|
types << own if own
|
|
87
|
-
by_path
|
|
88
|
-
next if value_path == path
|
|
89
|
-
|
|
90
|
-
values.each { |value| types << value_type(value) }
|
|
91
|
-
end
|
|
218
|
+
add_entry_types(types, by_path, own || owner == class_name ? path : nil)
|
|
92
219
|
end
|
|
93
220
|
types.empty? ? nil : Type::Combinator.union(*types)
|
|
94
221
|
end
|
|
95
222
|
|
|
223
|
+
# Each census entry's value type, except those of `skipped_path` (the file whose census already holds them).
|
|
224
|
+
def add_entry_types(types, by_path, skipped_path)
|
|
225
|
+
by_path.each do |value_path, values|
|
|
226
|
+
next if value_path == skipped_path
|
|
227
|
+
|
|
228
|
+
values.each { |value| types << value_type(value) }
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
96
232
|
# A String literal joins as `String`, not its `Constant`: a write elsewhere cannot see an in-place mutation of
|
|
97
233
|
# the String (`@s << "abc"`) in this file, in another or in an ancestor, so the value would be pinned too
|
|
98
234
|
# narrowly. Every other literal kind is immutable and joins as its `Constant`.
|
|
@@ -114,21 +250,45 @@ module Rigor
|
|
|
114
250
|
# `{ owner => { ivar => { path => [value, ...] } } }` over `census`'s path-tagged `:own_write_type` entries,
|
|
115
251
|
# built once per census and kept in one run-scoped slot keyed by its identity.
|
|
116
252
|
def table(census)
|
|
117
|
-
|
|
118
|
-
|
|
253
|
+
return build_table(census) if MemoAudit.enabled? && MemoAudit.suspended?
|
|
254
|
+
|
|
255
|
+
state = TypingState.current
|
|
256
|
+
slot = state[TABLE]
|
|
257
|
+
if slot && slot[0].equal?(census)
|
|
258
|
+
return MemoAudit.check("ivar contributions table", slot[1], census.size) { build_table(census) }
|
|
259
|
+
end
|
|
119
260
|
|
|
261
|
+
table = MemoAudit.key_audit("ivar contributions table") { build_table(census) }
|
|
262
|
+
state[TABLE] = [census, table]
|
|
263
|
+
table
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Issue #2983 — `table[BINDINGS]` lists `[class candidate key, constant name, kind]` over the census's
|
|
267
|
+
# `[:binds_class, …]` entries, and `table[CANDIDATE_KEYS]` the owners that are candidate keys; no class is named
|
|
268
|
+
# by a Symbol.
|
|
269
|
+
def build_table(census)
|
|
120
270
|
table = {}
|
|
121
271
|
canonical_paths = {}
|
|
122
272
|
census.each do |entry|
|
|
273
|
+
next (table[BINDINGS] ||= []) << entry.drop(1) if entry[0] == :binds_class
|
|
123
274
|
next unless entry.size == 5 && entry[0] == :own_write_type
|
|
124
275
|
|
|
125
|
-
by_path = ((table
|
|
276
|
+
by_path = (owner_slot(table, entry[1])[entry[2]] ||= {})
|
|
126
277
|
(by_path[canonical_paths[entry[4]] ||= canonical(entry[4])] ||= []) << entry[3]
|
|
127
278
|
end
|
|
128
|
-
Thread.current[TABLE_KEY] = [census, table]
|
|
129
279
|
table
|
|
130
280
|
end
|
|
131
|
-
|
|
281
|
+
|
|
282
|
+
def owner_slot(table, owner)
|
|
283
|
+
table[owner] ||= begin
|
|
284
|
+
(table[CANDIDATE_KEYS] ||= []) << owner if owner.include?(SEPARATOR) || owner.start_with?(OBJECT_MARK)
|
|
285
|
+
{}
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
private_class_method :chain_owners, :ancestor_names, :basic_object_edge?, :resolution, :resolve, :declared?,
|
|
290
|
+
:memoized_resolution, :module?, :any_ivar_types, :contributed, :add_entry_types,
|
|
291
|
+
:value_type, :canonical, :table, :build_table, :owner_slot
|
|
132
292
|
end
|
|
133
293
|
end
|
|
134
294
|
end
|