rigortype 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +59 -3
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -22,12 +22,52 @@ module Rigor
|
|
|
22
22
|
# It is a STACK because captures nest (a memoised callee body evaluating another memo-miss callee): an
|
|
23
23
|
# outer capture must also see the inner window's reads, or its read-set would be non-transitive and a
|
|
24
24
|
# later replay would drop the nested callee's edges.
|
|
25
|
+
#
|
|
26
|
+
# Every event reaches every capture open at the time, so an inner capture's sets are always a subset of
|
|
27
|
+
# each outer one's: the inner capture was pushed after the outer, and everything added since reached both.
|
|
28
|
+
# {withhold} swaps in a fresh stack and restores this one untouched, so it keeps the property too.
|
|
29
|
+
# {tee_read} and {tee_missing} rely on it to stop at the first capture that already holds an event.
|
|
25
30
|
CAPTURE_KEY = :__rigor_dependency_capture_stack__
|
|
26
31
|
private_constant :CAPTURE_KEY
|
|
27
32
|
|
|
33
|
+
# The read sets an {Accumulator} or {Capture} already holds in full, by identity: replayed into it, or
|
|
34
|
+
# captured by a window it saw every event of ({capture}). Both only ever grow and a {ReadSet} is frozen,
|
|
35
|
+
# so replaying such a set into the same target again adds nothing. Recording replays the same stored
|
|
36
|
+
# read set many times over: on Rigor's own `lib`, 71% of the replays into a consumer repeated one
|
|
37
|
+
# already made into it, carrying 65% of the replayed events.
|
|
38
|
+
module ReplayMemory
|
|
39
|
+
def holds?(read_set)
|
|
40
|
+
!@held.nil? && @held.key?(read_set)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Called only once every event of `read_set` is in, so a replay that raises part-way leaves the
|
|
44
|
+
# target unmarked and the next replay adds the rest.
|
|
45
|
+
def hold!(read_set)
|
|
46
|
+
(@held ||= {}.compare_by_identity)[read_set] = true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# #1590 — the resolution-chain filings ({file_chain_once}) an {Accumulator} or {Capture} already holds in
|
|
51
|
+
# full: per chain (by identity; the table holds the chain, so its identity cannot be reused while the target
|
|
52
|
+
# lives), the starts filed from it and the discovery index each was filed against. A filing is a pure
|
|
53
|
+
# function of those three, so filing it again into the same target adds nothing.
|
|
54
|
+
module ChainMemory
|
|
55
|
+
def chain_filed?(chain, start, discovery)
|
|
56
|
+
table = @chains&.[](chain)
|
|
57
|
+
!table.nil? && table.key?(start) && table[start].equal?(discovery)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def chain_filed!(chain, start, discovery)
|
|
61
|
+
((@chains ||= {}.compare_by_identity)[chain] ||= {})[start] = discovery
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
28
65
|
# Mutable per-consumer accumulator. Frozen into a {Record} snapshot when `record_for` returns.
|
|
29
66
|
class Accumulator
|
|
30
|
-
|
|
67
|
+
include ReplayMemory
|
|
68
|
+
include ChainMemory
|
|
69
|
+
|
|
70
|
+
attr_reader :consumer, :sources, :missing, :symbol_sources, :ancestry_sources, :suspensions
|
|
31
71
|
|
|
32
72
|
def initialize(consumer)
|
|
33
73
|
@consumer = consumer
|
|
@@ -39,6 +79,15 @@ module Rigor
|
|
|
39
79
|
# file-granularity by nature (a superclass edge touches the whole class).
|
|
40
80
|
@symbol_sources = Hash.new { |h, k| h[k] = Set.new }
|
|
41
81
|
@ancestry_sources = Set.new
|
|
82
|
+
@held = nil
|
|
83
|
+
@chains = nil
|
|
84
|
+
@suspensions = 0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Counts the {record_for} windows of another consumer opened while this one was recording, which is
|
|
88
|
+
# when a capture window stops seeing only this consumer's events.
|
|
89
|
+
def suspend!
|
|
90
|
+
@suspensions += 1
|
|
42
91
|
end
|
|
43
92
|
|
|
44
93
|
def snapshot
|
|
@@ -65,16 +114,28 @@ module Rigor
|
|
|
65
114
|
# self-read filter is applied at {replay} time, mirroring {read_site}.
|
|
66
115
|
ReadSet = Data.define(:reads, :missing)
|
|
67
116
|
|
|
117
|
+
# What every window that saw nothing closes into: most windows {RbsDispatch}'s recording-mode memos
|
|
118
|
+
# open see nothing, and they need neither a copy of two empty Sets nor a place in a target's
|
|
119
|
+
# {ReplayMemory}.
|
|
120
|
+
EMPTY_READ_SET = Ractor.make_shareable(ReadSet.new(reads: Set.new, missing: Set.new))
|
|
121
|
+
|
|
68
122
|
# Mutable capture accumulator; snapshot into a frozen {ReadSet} when the capture window closes.
|
|
69
123
|
class Capture
|
|
124
|
+
include ReplayMemory
|
|
125
|
+
include ChainMemory
|
|
126
|
+
|
|
70
127
|
attr_reader :reads, :missing
|
|
71
128
|
|
|
72
129
|
def initialize
|
|
73
130
|
@reads = Set.new
|
|
74
131
|
@missing = Set.new
|
|
132
|
+
@held = nil
|
|
133
|
+
@chains = nil
|
|
75
134
|
end
|
|
76
135
|
|
|
77
136
|
def snapshot
|
|
137
|
+
return EMPTY_READ_SET if reads.empty? && missing.empty?
|
|
138
|
+
|
|
78
139
|
ReadSet.new(reads: reads.dup.freeze, missing: missing.dup.freeze)
|
|
79
140
|
end
|
|
80
141
|
end
|
|
@@ -94,6 +155,7 @@ module Rigor
|
|
|
94
155
|
# the previous recorder on exit.
|
|
95
156
|
def record_for(consumer)
|
|
96
157
|
previous = Thread.current[KEY]
|
|
158
|
+
previous&.suspend!
|
|
97
159
|
accumulator = Accumulator.new(consumer.to_s)
|
|
98
160
|
Thread.current[KEY] = accumulator
|
|
99
161
|
@mutex.synchronize { @active_count += 1 }
|
|
@@ -109,6 +171,13 @@ module Rigor
|
|
|
109
171
|
@active_count.positive?
|
|
110
172
|
end
|
|
111
173
|
|
|
174
|
+
# Whether THIS thread's reads reach an accumulator: inside {record_for}, including a {withhold}
|
|
175
|
+
# window. {active?} answers for the process and is the cheap gate; this is the exact one, for a caller
|
|
176
|
+
# that must know whether an empty {capture} means "nothing to record" or "nothing was watching".
|
|
177
|
+
def recording?
|
|
178
|
+
!Thread.current[KEY].nil?
|
|
179
|
+
end
|
|
180
|
+
|
|
112
181
|
# ADR-84 WD2 — runs the block under an observe-and-forward capture window and returns
|
|
113
182
|
# `[block_result, read_set]`. Reads recorded during the window reach the current consumer's accumulator
|
|
114
183
|
# unchanged (forwarding) and are additionally collected into the returned frozen {ReadSet} (observing),
|
|
@@ -116,6 +185,8 @@ module Rigor
|
|
|
116
185
|
# {active?}; opening a capture with no accumulator active would collect reads no run is recording.
|
|
117
186
|
def capture
|
|
118
187
|
captures = (Thread.current[CAPTURE_KEY] ||= [])
|
|
188
|
+
accumulator = Thread.current[KEY]
|
|
189
|
+
suspensions = accumulator&.suspensions
|
|
119
190
|
capture = Capture.new
|
|
120
191
|
captures.push(capture)
|
|
121
192
|
begin
|
|
@@ -123,9 +194,28 @@ module Rigor
|
|
|
123
194
|
ensure
|
|
124
195
|
captures.pop
|
|
125
196
|
end
|
|
126
|
-
|
|
197
|
+
read_set = capture.snapshot
|
|
198
|
+
note_captured(read_set, captures, accumulator, suspensions)
|
|
199
|
+
[result, read_set]
|
|
127
200
|
end
|
|
128
201
|
|
|
202
|
+
# Every event a window saw also reached the window outside it, and was filed for the consumer that was
|
|
203
|
+
# recording at the time, except the consumer's own self-reads, which a replay into that consumer drops
|
|
204
|
+
# anyway. So the enclosing window holds the closed window's read set in full, and so does the consumer's
|
|
205
|
+
# accumulator when no other consumer recorded in between. Noting that lets a later replay of the set
|
|
206
|
+
# there skip a pass that could add nothing: on Rigor's own `lib`, two thirds of the first replays into a
|
|
207
|
+
# consumer were of a set captured while that consumer was recording.
|
|
208
|
+
def note_captured(read_set, captures, accumulator, suspensions)
|
|
209
|
+
return if read_set.equal?(EMPTY_READ_SET)
|
|
210
|
+
|
|
211
|
+
captures.last&.hold!(read_set)
|
|
212
|
+
return unless accumulator && Thread.current[KEY].equal?(accumulator)
|
|
213
|
+
return unless accumulator.suspensions == suspensions
|
|
214
|
+
|
|
215
|
+
accumulator.hold!(read_set)
|
|
216
|
+
end
|
|
217
|
+
private_class_method :note_captured
|
|
218
|
+
|
|
129
219
|
# Issue #992 — runs the block with its reads DETACHED from the current consumer (and from any enclosing
|
|
130
220
|
# capture) and returns `[block_result, read_set]`, so a caller that learns only afterwards whether its
|
|
131
221
|
# answer depended on those reads can {replay} them or drop them. `call.wrong-arity` reads a class's whole
|
|
@@ -154,22 +244,75 @@ module Rigor
|
|
|
154
244
|
# capture so an enclosing capture window stays transitive when a memo hit substitutes for a body walk.
|
|
155
245
|
def replay(read_set)
|
|
156
246
|
accumulator = Thread.current[KEY]
|
|
157
|
-
return if accumulator.nil? || read_set.nil?
|
|
247
|
+
return if accumulator.nil? || read_set.nil? || read_set.equal?(EMPTY_READ_SET)
|
|
158
248
|
|
|
159
249
|
captures = Thread.current[CAPTURE_KEY]
|
|
250
|
+
tee_read_set(captures, read_set) if captures && !captures.empty?
|
|
251
|
+
# Each set keeps its own insertion order whichever target is filled first, and a skipped replay is one
|
|
252
|
+
# that could only have re-added what the target already holds.
|
|
253
|
+
return if accumulator.holds?(read_set)
|
|
254
|
+
|
|
255
|
+
consumer = accumulator.consumer
|
|
160
256
|
read_set.reads.each do |pair|
|
|
161
|
-
captures&.each { |c| c.reads.add(pair) }
|
|
162
257
|
path, symbol = pair
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
accumulate_read(accumulator, path, symbol)
|
|
258
|
+
accumulate_read(accumulator, path, symbol) unless path == consumer
|
|
166
259
|
end
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
260
|
+
missing = accumulator.missing
|
|
261
|
+
read_set.missing.each { |entry| missing << entry }
|
|
262
|
+
accumulator.hold!(read_set)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# #1590 — runs the block, which files a resolution chain's edges from entry `start` (nil: the root and the
|
|
266
|
+
# whole chain) against `discovery`, unless the current consumer's accumulator AND the innermost open capture
|
|
267
|
+
# already hold that filing; the block files the same edges every time, so skipping it there changes nothing.
|
|
268
|
+
# Every event reaches every capture open at the time (see CAPTURE_KEY), so the innermost capture holding the
|
|
269
|
+
# filing means every open capture does. A filing the accumulator holds but a capture opened since does not is
|
|
270
|
+
# filed again, so that capture's read set, replayed elsewhere, still carries it; and {withhold} starts a fresh
|
|
271
|
+
# accumulator and capture, which hold nothing. The marks are made only once the block has run, as {hold!}'s are.
|
|
272
|
+
def file_chain_once(chain, start, discovery)
|
|
273
|
+
accumulator = Thread.current[KEY]
|
|
274
|
+
return if accumulator.nil?
|
|
275
|
+
|
|
276
|
+
captures = Thread.current[CAPTURE_KEY]
|
|
277
|
+
open = captures && !captures.empty?
|
|
278
|
+
if accumulator.chain_filed?(chain, start, discovery) &&
|
|
279
|
+
(!open || captures.last.chain_filed?(chain, start, discovery))
|
|
280
|
+
return
|
|
170
281
|
end
|
|
282
|
+
|
|
283
|
+
yield
|
|
284
|
+
accumulator.chain_filed!(chain, start, discovery)
|
|
285
|
+
captures.each { |capture| capture.chain_filed!(chain, start, discovery) } if open
|
|
171
286
|
end
|
|
172
287
|
|
|
288
|
+
# {replay}'s share for the open captures. The innermost capture having seen `read_set` already means every
|
|
289
|
+
# open capture holds all of it (see CAPTURE_KEY).
|
|
290
|
+
def tee_read_set(captures, read_set)
|
|
291
|
+
innermost = captures.last
|
|
292
|
+
return if innermost.holds?(read_set)
|
|
293
|
+
|
|
294
|
+
read_set.reads.each { |pair| tee_read(captures, pair) }
|
|
295
|
+
read_set.missing.each { |entry| tee_missing(captures, entry) }
|
|
296
|
+
innermost.hold!(read_set)
|
|
297
|
+
end
|
|
298
|
+
private_class_method :tee_read_set
|
|
299
|
+
|
|
300
|
+
# Adds a read pair to the open captures, innermost first, and stops at the first capture that already holds
|
|
301
|
+
# it: every capture outside that one holds it too (see CAPTURE_KEY). The capture sets end up exactly as
|
|
302
|
+
# adding it to every capture would leave them, in the same insertion order.
|
|
303
|
+
def tee_read(captures, pair)
|
|
304
|
+
index = captures.size - 1
|
|
305
|
+
index -= 1 while index >= 0 && captures[index].reads.add?(pair)
|
|
306
|
+
end
|
|
307
|
+
private_class_method :tee_read
|
|
308
|
+
|
|
309
|
+
# {tee_read} for a name-keyed `missing` entry.
|
|
310
|
+
def tee_missing(captures, entry)
|
|
311
|
+
index = captures.size - 1
|
|
312
|
+
index -= 1 while index >= 0 && captures[index].missing.add?(entry)
|
|
313
|
+
end
|
|
314
|
+
private_class_method :tee_missing
|
|
315
|
+
|
|
173
316
|
# Records that the current consumer read a declaration / body whose definition site is `path_line` (a
|
|
174
317
|
# `"path:line"` String, or nil). When `symbol` is given (a `"ClassName#method"` String), the read is a
|
|
175
318
|
# method-call edge and is recorded at symbol granularity in `symbol_sources` in addition to the coarse
|
|
@@ -181,16 +324,26 @@ module Rigor
|
|
|
181
324
|
accumulator = Thread.current[KEY]
|
|
182
325
|
return if accumulator.nil? || path_line.nil?
|
|
183
326
|
|
|
184
|
-
path = path_line
|
|
327
|
+
path = site_path(path_line)
|
|
185
328
|
return unless path
|
|
186
329
|
|
|
187
330
|
captures = Thread.current[CAPTURE_KEY]
|
|
188
|
-
captures
|
|
331
|
+
tee_read(captures, [path, symbol].freeze) if captures && !captures.empty?
|
|
189
332
|
return if path == accumulator.consumer
|
|
190
333
|
|
|
191
334
|
accumulate_read(accumulator, path, symbol)
|
|
192
335
|
end
|
|
193
336
|
|
|
337
|
+
# The path part of a `"path:line"` site: what `path_line.split(":", 2).first` answers, without the Array
|
|
338
|
+
# and the discarded line String.
|
|
339
|
+
def site_path(path_line)
|
|
340
|
+
colon = path_line.byteindex(":")
|
|
341
|
+
return path_line.byteslice(0, colon) if colon
|
|
342
|
+
|
|
343
|
+
path_line.empty? ? nil : path_line.dup
|
|
344
|
+
end
|
|
345
|
+
private_class_method :site_path
|
|
346
|
+
|
|
194
347
|
# Records a NAME-KEYED cross-file dependency: this consumer's answer depends on what the project makes
|
|
195
348
|
# of `name` under `kind`, so a recheck must re-analyze it when that changes. The recorded key is
|
|
196
349
|
# inverted into `negative_dependents` and matched against the producer for its kind.
|
|
@@ -206,10 +359,53 @@ module Rigor
|
|
|
206
359
|
return if accumulator.nil?
|
|
207
360
|
|
|
208
361
|
entry = "#{kind}:#{name}"
|
|
209
|
-
Thread.current[CAPTURE_KEY]
|
|
362
|
+
captures = Thread.current[CAPTURE_KEY]
|
|
363
|
+
tee_missing(captures, entry) if captures && !captures.empty?
|
|
210
364
|
accumulator.missing.add(entry)
|
|
211
365
|
end
|
|
212
366
|
|
|
367
|
+
# {read_name} keyed on the LAST SEGMENT of a constant path, `name.delete_prefix("::").split("::").last`,
|
|
368
|
+
# which is the key the `class:` and `constant:` name edges use. The segment is found by scanning for the
|
|
369
|
+
# last separator rather than by building every segment: on Rigor's own `lib` that split cost about seven
|
|
370
|
+
# objects a call over half a million calls. A name the scan could read differently from `split` (one
|
|
371
|
+
# that is not ASCII, or carries a run of colons or a trailing one) takes `split`'s answer.
|
|
372
|
+
def read_last_segment(kind, name)
|
|
373
|
+
return if Thread.current[KEY].nil?
|
|
374
|
+
|
|
375
|
+
segment = last_segment(name)
|
|
376
|
+
read_name(kind, segment) if segment
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def last_segment(name)
|
|
380
|
+
return name.delete_prefix("::").split("::").last unless plain_constant_path?(name)
|
|
381
|
+
|
|
382
|
+
start = name.start_with?("::") ? 2 : 0
|
|
383
|
+
separator = name.rindex("::")
|
|
384
|
+
cut = separator && separator >= start ? separator + 2 : start
|
|
385
|
+
cut == name.bytesize ? nil : name.byteslice(cut, name.bytesize - cut)
|
|
386
|
+
end
|
|
387
|
+
private_class_method :last_segment
|
|
388
|
+
|
|
389
|
+
def plain_constant_path?(name)
|
|
390
|
+
name.ascii_only? && !name.end_with?(":") && !name.include?(":::")
|
|
391
|
+
end
|
|
392
|
+
private_class_method :plain_constant_path?
|
|
393
|
+
|
|
394
|
+
# {read_name} for prebuilt, frozen `"kind:name"` keys: a caller that files the same keys on every read
|
|
395
|
+
# (a resolution chain's hook edges, #1641) builds them once and adds them here without a String apiece.
|
|
396
|
+
def read_keys(keys)
|
|
397
|
+
accumulator = Thread.current[KEY]
|
|
398
|
+
return if accumulator.nil?
|
|
399
|
+
|
|
400
|
+
captures = Thread.current[CAPTURE_KEY]
|
|
401
|
+
open = captures && !captures.empty?
|
|
402
|
+
missing = accumulator.missing
|
|
403
|
+
keys.each do |entry|
|
|
404
|
+
tee_missing(captures, entry) if open
|
|
405
|
+
missing.add(entry)
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
213
409
|
# The miss-only spelling of {read_name}: a cross-file lookup of `name` (kind `:method` / `:class` /
|
|
214
410
|
# `:toplevel` / …) that resolved to nothing. Kept as the name the internal spec's negative-edge
|
|
215
411
|
# contract uses.
|
|
@@ -217,13 +413,14 @@ module Rigor
|
|
|
217
413
|
read_name(kind, name)
|
|
218
414
|
end
|
|
219
415
|
|
|
220
|
-
# The positive-edge aggregation shared by {read_site} and {replay}.
|
|
416
|
+
# The positive-edge aggregation shared by {read_site} and {replay}. `sources` is the union of the other
|
|
417
|
+
# two tables' paths, and this is the only writer of all three, so a read the finer table already holds is
|
|
418
|
+
# already in `sources` and costs one Set probe instead of two.
|
|
221
419
|
def accumulate_read(accumulator, path, symbol)
|
|
222
|
-
accumulator.sources << path
|
|
223
420
|
if symbol
|
|
224
|
-
accumulator.symbol_sources[path]
|
|
225
|
-
|
|
226
|
-
accumulator.
|
|
421
|
+
accumulator.sources << path if accumulator.symbol_sources[path].add?(symbol)
|
|
422
|
+
elsif accumulator.ancestry_sources.add?(path)
|
|
423
|
+
accumulator.sources << path
|
|
227
424
|
end
|
|
228
425
|
end
|
|
229
426
|
private_class_method :accumulate_read
|
|
@@ -42,6 +42,9 @@ module Rigor
|
|
|
42
42
|
@class_to_gem = {}
|
|
43
43
|
@budget_exceeded = []
|
|
44
44
|
@gem_modes = {}
|
|
45
|
+
@refinements = {}
|
|
46
|
+
@bound_constants = Set.new
|
|
47
|
+
@bound_segments = Set.new
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def absorb(outcome, budget, builder)
|
|
@@ -58,6 +61,9 @@ module Rigor
|
|
|
58
61
|
@gem_modes[resolved.gem_name] = resolved.mode
|
|
59
62
|
walked = builder.walker_outcome_for(resolved, budget)
|
|
60
63
|
@catalog.merge!(walked.catalog)
|
|
64
|
+
@refinements = Inference::ScopeIndexer.merge_refinement_tables(@refinements, walked.refinements)
|
|
65
|
+
@bound_constants.merge(walked.bound)
|
|
66
|
+
@bound_segments.merge(walked.bound_segments)
|
|
61
67
|
builder.record_class_to_gem(walked.catalog, resolved.gem_name, @class_to_gem)
|
|
62
68
|
@budget_exceeded << resolved.gem_name if walked.truncated?
|
|
63
69
|
end
|
|
@@ -68,7 +74,8 @@ module Rigor
|
|
|
68
74
|
method_catalog: @catalog, budget_exceeded: @budget_exceeded,
|
|
69
75
|
class_to_gem: @class_to_gem,
|
|
70
76
|
budget_overrun_strategy: dependencies.budget_overrun_strategy,
|
|
71
|
-
gem_modes: @gem_modes
|
|
77
|
+
gem_modes: @gem_modes, refinements: @refinements, bound_constants: @bound_constants,
|
|
78
|
+
bound_segments: @bound_segments
|
|
72
79
|
)
|
|
73
80
|
end
|
|
74
81
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../cache/descriptor"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
module Analysis
|
|
5
7
|
module DependencySourceInference
|
|
@@ -19,6 +21,11 @@ module Rigor
|
|
|
19
21
|
def descriptor_key
|
|
20
22
|
[gem_name, version, mode].freeze
|
|
21
23
|
end
|
|
24
|
+
|
|
25
|
+
# ADR-10 slice 3 — this gem's row of a cache descriptor: its name, resolved version and mode.
|
|
26
|
+
def dependency_entry
|
|
27
|
+
Cache::Descriptor::DependencyEntry.new(gem_name: gem_name, gem_version: version, mode: mode)
|
|
28
|
+
end
|
|
22
29
|
end
|
|
23
30
|
|
|
24
31
|
# Unresolvable reasons. `:not_in_bundle` covers both the "RubyGems doesn't know this gem" case and the
|
|
@@ -43,6 +50,20 @@ module Rigor
|
|
|
43
50
|
)
|
|
44
51
|
end
|
|
45
52
|
|
|
53
|
+
# Issue #1814 — the {Resolved#dependency_entry} of each non-disabled `dependencies.source_inference` entry that
|
|
54
|
+
# resolves, in configuration order, without walking any gem. The run-result cache key and the incremental
|
|
55
|
+
# snapshot's fingerprint are built before analysis from inputs that are cheap to read, and a gem's walked
|
|
56
|
+
# source (its method catalogue, its refinement table, the constants it binds) is an input of the run that no
|
|
57
|
+
# analysed file records; a `Gemfile.lock` digest covers a `bundle update` and nothing else, so a project with no
|
|
58
|
+
# lockfile, or a gem installed beside the bundle, would replay the diagnostics of the gem's previous version.
|
|
59
|
+
# Unresolvable entries have no version to key on and add nothing: the gem appearing later adds its row.
|
|
60
|
+
def dependency_entries(dependencies)
|
|
61
|
+
dependencies.source_inference.reject(&:disabled?).filter_map do |entry|
|
|
62
|
+
resolved = resolve(entry)
|
|
63
|
+
resolved.dependency_entry if resolved.is_a?(Resolved)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
46
67
|
# Locator. `Gem.loaded_specs` reflects the bundle (cheap lookup, no filesystem walk); `find_by_name` is
|
|
47
68
|
# the broader fallback for gems present on the gem path but not yet `require`'d. `Gem::MissingSpecError`
|
|
48
69
|
# is a `LoadError` subclass, so the rescue covers both missing-spec and load-error signals.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../cache/descriptor"
|
|
4
|
+
require_relative "../../inference/scope_indexer"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Analysis
|
|
@@ -11,7 +12,8 @@ module Rigor
|
|
|
11
12
|
# entries.
|
|
12
13
|
class Index
|
|
13
14
|
attr_reader :resolved_gems, :unresolvable, :method_catalog, :budget_exceeded,
|
|
14
|
-
:class_to_gem, :budget_overrun_strategy, :gem_modes
|
|
15
|
+
:class_to_gem, :budget_overrun_strategy, :gem_modes, :refinements, :bound_constants,
|
|
16
|
+
:bound_segments
|
|
15
17
|
|
|
16
18
|
# @param method_catalog — => Symbol}] the flat
|
|
17
19
|
# `(class_name, method_name) → :instance | :singleton` table produced by {Walker.walk}, aggregated
|
|
@@ -30,10 +32,21 @@ module Rigor
|
|
|
30
32
|
# gem-source and RBS both contribute under `mode: :full`. The map is keyed on `gem_name` (not
|
|
31
33
|
# class) because re-opened classes belong to the first gem they appeared in per `class_to_gem`;
|
|
32
34
|
# `mode_for(class_name)` chains the two lookups.
|
|
33
|
-
|
|
35
|
+
# @param refinements — issue #1672: the opt-in gems' `refine X do … end` bodies, `{refined class =>
|
|
36
|
+
# {method => [refining modules]}}` ({Walker::Outcome#refinements}). The runner merges it into the
|
|
37
|
+
# project's `discovered_refinements` seed, so `using M` for a gem's `M` reaches the check rules.
|
|
38
|
+
# @param bound_constants — issue #1814: every name the opt-in gems' files bind by a constant write without
|
|
39
|
+
# declaring a class or module by it ({Walker::Outcome#bound}: `Shared::Target` for `module Shared; Target =
|
|
40
|
+
# String; end`), unioned across the walked gems. A refine key one of them binds may name any value
|
|
41
|
+
# (`refine Shared::Target` in the project or in the next gem), which the reader decides at read time
|
|
42
|
+
# ({Inference::InEffectRefinements.class_wildcard_key?}) through {#bound_constant?}.
|
|
43
|
+
# @param bound_segments — the last segment of each such write whose owner the walk cannot spell
|
|
44
|
+
# ({Walker::Outcome#bound_segments}: a `const_set`), which matches that segment of any key.
|
|
45
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
34
46
|
resolved_gems: [], unresolvable: [], method_catalog: {},
|
|
35
47
|
budget_exceeded: [], class_to_gem: {},
|
|
36
|
-
budget_overrun_strategy: :walker_cap, gem_modes: {}
|
|
48
|
+
budget_overrun_strategy: :walker_cap, gem_modes: {}, refinements: {}, bound_constants: Set.new,
|
|
49
|
+
bound_segments: Set.new
|
|
37
50
|
)
|
|
38
51
|
@resolved_gems = resolved_gems.freeze
|
|
39
52
|
@unresolvable = unresolvable.freeze
|
|
@@ -42,6 +55,9 @@ module Rigor
|
|
|
42
55
|
@class_to_gem = class_to_gem.freeze
|
|
43
56
|
@budget_overrun_strategy = budget_overrun_strategy
|
|
44
57
|
@gem_modes = gem_modes.freeze
|
|
58
|
+
@refinements = Inference::ScopeIndexer.freeze_refinements(refinements)
|
|
59
|
+
@bound_constants = bound_constants.to_set.freeze
|
|
60
|
+
@bound_segments = bound_segments.to_set.freeze
|
|
45
61
|
freeze
|
|
46
62
|
end
|
|
47
63
|
|
|
@@ -62,6 +78,22 @@ module Rigor
|
|
|
62
78
|
@class_to_gem[class_name]
|
|
63
79
|
end
|
|
64
80
|
|
|
81
|
+
# Issue #1814 — does a walked gem bind `name` (a refine key, or a prefix of one: `Shared::Target`, `Pag::Enc`)
|
|
82
|
+
# by a constant write that declares no class or module, or bind a constant of `name`'s last segment under an
|
|
83
|
+
# owner the walk could not spell? Such a key may name any value (`module Shared; Target = String; end` then
|
|
84
|
+
# `refine(Shared::Target)`), which the walker cannot resolve and the reader decides
|
|
85
|
+
# ({Inference::InEffectRefinements.class_wildcard_key?}). A gem constant that merely shares a core class's name
|
|
86
|
+
# (`Names::String`) binds nothing a sibling spelling of `refine String` (`Ext::String`) could denote.
|
|
87
|
+
def bound_constant?(name)
|
|
88
|
+
@bound_constants.include?(name) || @bound_segments.include?(name.split("::").last)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Does any walked gem bind a constant at all? False for a project that opts in no gem, so a reader asks nothing
|
|
92
|
+
# per key.
|
|
93
|
+
def binds_constants?
|
|
94
|
+
!(@bound_constants.empty? && @bound_segments.empty?)
|
|
95
|
+
end
|
|
96
|
+
|
|
65
97
|
# ADR-10 slice 5c — per-class mode lookup. Chains `class_to_gem` + `gem_modes`; returns `nil` when
|
|
66
98
|
# the class isn't owned by any opt-in gem in this run.
|
|
67
99
|
def mode_for(class_name)
|
|
@@ -99,14 +131,7 @@ module Rigor
|
|
|
99
131
|
# surfaces them as `dynamic.dependency-source.gem-not-found` diagnostics. Resolved-but-disabled
|
|
100
132
|
# entries are also absent: the {Builder} skips them before resolution, so they never reach the index.
|
|
101
133
|
def cache_descriptor
|
|
102
|
-
dependencies
|
|
103
|
-
Cache::Descriptor::DependencyEntry.new(
|
|
104
|
-
gem_name: resolved.gem_name,
|
|
105
|
-
gem_version: resolved.version,
|
|
106
|
-
mode: resolved.mode
|
|
107
|
-
)
|
|
108
|
-
end
|
|
109
|
-
Cache::Descriptor.new(dependencies: dependencies)
|
|
134
|
+
Cache::Descriptor.new(dependencies: @resolved_gems.map(&:dependency_entry))
|
|
110
135
|
end
|
|
111
136
|
end
|
|
112
137
|
|