rigortype 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +18 -5
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/02-cli-reference.md +19 -1
- data/docs/manual/03-configuration.md +7 -5
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +41 -4
- 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 +11 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +292 -59
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
- data/lib/rigor/analysis/incremental_session.rb +103 -23
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +241 -37
- data/lib/rigor/analysis/reachability/scan.rb +490 -77
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/run_cache_probe.rb +1 -1
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +62 -13
- data/lib/rigor/analysis/worker_session.rb +8 -5
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +55 -5
- 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 +140 -40
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +6 -3
- data/lib/rigor/cli/unused_command.rb +166 -27
- data/lib/rigor/cli.rb +39 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +61 -2
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +316 -14
- data/lib/rigor/environment/required_features.rb +208 -0
- data/lib/rigor/environment.rb +33 -8
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +43 -3
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +261 -29
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +286 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -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/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +96 -14
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +12 -7
- data/lib/rigor/inference/project_patched_scanner.rb +108 -26
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
- data/lib/rigor/inference/refined_dispatch.rb +320 -0
- data/lib/rigor/inference/repeated_or_writes.rb +2 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +827 -65
- data/lib/rigor/inference/statement_evaluator.rb +294 -51
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +87 -5
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +118 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +433 -0
- data/lib/rigor/lens/members.rb +153 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +280 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +40 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +48 -6
- data/lib/rigor/protection/mutation_cache.rb +3 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +211 -21
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +100 -10
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +30 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +9 -4
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +83 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
class Environment
|
|
7
|
+
# Issue #1713 — loading a {RequiredFeatures}-gated vendored directory minus only what another signature
|
|
8
|
+
# source already declares, instead of standing the whole directory down.
|
|
9
|
+
#
|
|
10
|
+
# `RbsLoader.build_env_for` first tries the directory whole. When that costs the environment something
|
|
11
|
+
# (`RbsLoader.gated_failures`), it builds the environment WITHOUT the directory and plans against it with
|
|
12
|
+
# {.build}: the plan is the directory's parsed declarations, rewritten in memory (the files on disk are never
|
|
13
|
+
# touched), so that
|
|
14
|
+
#
|
|
15
|
+
# - a method, attribute or alias another source declares on the same class is left out (an overload
|
|
16
|
+
# continuation, `def m: ... | ...`, declares nothing of its own and needs the vendored base, so it keeps
|
|
17
|
+
# it); a member declaring two methods loses only the one taken — an `attr_accessor` narrows to its reader
|
|
18
|
+
# or writer, a `def self?.m` to its instance or singleton side;
|
|
19
|
+
# - a type whose header disagrees with another source's — a class one side declares as a module, or a
|
|
20
|
+
# different generic arity — stands down as a whole: its declaration becomes a *shell*, the other source's
|
|
21
|
+
# header with no members and no superclass, which keeps the vendored types nested inside it in their
|
|
22
|
+
# lexical context;
|
|
23
|
+
# - a type in `shelled` (the types the trial build of a previous plan still failed for) is shelled the same
|
|
24
|
+
# way when another source declares it. When none does — the type failed because something it names was
|
|
25
|
+
# displaced, a superclass the project redeclared as a module, say — it keeps its own members and nested
|
|
26
|
+
# types but loses its superclass, and is stamped as a Rigor-synthesized stub type
|
|
27
|
+
# (`RbsLoader::SYNTHETIC_STUB_BUFFER`): its own members still type, and a call it no longer answers reads
|
|
28
|
+
# `Dynamic[top]` rather than drawing `call.undefined-method` for a method it inherits at runtime.
|
|
29
|
+
#
|
|
30
|
+
# The kept declarations load where the directory would have loaded, ahead of the project's signatures, so
|
|
31
|
+
# which declaration is a class's primary one does not move; the shells load after them, so a shell never
|
|
32
|
+
# becomes the primary declaration of a class the project declares. The trial build stays the arbiter:
|
|
33
|
+
# `build_env_for` keeps a plan's environment only when every gated type builds in it.
|
|
34
|
+
#
|
|
35
|
+
# {.standdowns} reads what stood down back off a built environment, the way the other `rbs.coverage.*`
|
|
36
|
+
# notices are derived, so a warm run off the env cache reports what the cold run reported.
|
|
37
|
+
module GatedSignaturePlan
|
|
38
|
+
# Buffer name stamped on the shell declarations. Never a real path, so {.standdowns} tells a shell from the
|
|
39
|
+
# vendored declaration it replaced by it.
|
|
40
|
+
SHELL_BUFFER = "(rigor: vendored stand-down shells)"
|
|
41
|
+
|
|
42
|
+
# The buffer names Rigor synthesizes declarations under. They stand for no source, so neither a plan nor
|
|
43
|
+
# a stand-down report reads them as another source's declaration.
|
|
44
|
+
def self.synthetic_buffers
|
|
45
|
+
[SHELL_BUFFER, RbsLoader::SYNTHETIC_NAMESPACE_BUFFER, RbsLoader::SYNTHETIC_STUB_BUFFER]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# A planned load: `leading` and `shells` are `[buffer, directives, decls]` sources, `changed` is false
|
|
49
|
+
# when the plan is the directory as it stands.
|
|
50
|
+
Plan = Data.define(:leading, :shells, :changed)
|
|
51
|
+
|
|
52
|
+
# One plan's walk over the vendored declarations: the environment it plans against, the types to shell
|
|
53
|
+
# whatever their headers, and whether anything was left out.
|
|
54
|
+
class Planner
|
|
55
|
+
attr_reader :changed
|
|
56
|
+
|
|
57
|
+
def initialize(base_env, shelled)
|
|
58
|
+
@base_env = base_env
|
|
59
|
+
@shelled = shelled
|
|
60
|
+
@changed = false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Splits `decls` into what loads ahead of the project's signatures and what loads after them.
|
|
64
|
+
def plan_decls(decls, namespace)
|
|
65
|
+
lead = []
|
|
66
|
+
shell = []
|
|
67
|
+
decls.each do |decl|
|
|
68
|
+
unless GatedSignaturePlan.type_declaration?(decl)
|
|
69
|
+
lead << decl
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
kept, shelled = plan_type(decl, GatedSignaturePlan.qualify(namespace, decl.name))
|
|
74
|
+
lead << kept if kept
|
|
75
|
+
shell << shelled if shelled
|
|
76
|
+
end
|
|
77
|
+
[lead, shell]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def plan_type(decl, name)
|
|
83
|
+
others = GatedSignaturePlan.other_declarations(@base_env, name)
|
|
84
|
+
nested = decl.members.select { |member| GatedSignaturePlan.type_declaration?(member) }
|
|
85
|
+
nested_lead, nested_shell = plan_decls(nested, name)
|
|
86
|
+
if GatedSignaturePlan.header_conflict?(decl, others) || (@shelled.include?(name) && !others.empty?)
|
|
87
|
+
@changed = true
|
|
88
|
+
return [nil, GatedSignaturePlan.header_copy(others.first, decl.name, nested_lead + nested_shell)]
|
|
89
|
+
end
|
|
90
|
+
return [nil, open_type(decl, nested_lead + nested_shell)] if @shelled.include?(name)
|
|
91
|
+
|
|
92
|
+
wrapper = nested_shell.empty? ? nil : GatedSignaturePlan.header_copy(decl, decl.name, nested_shell)
|
|
93
|
+
[keep_type(decl, others, nested_lead), wrapper]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# `decl` less the methods `others` declare, with its nested types replaced by their planned kept halves.
|
|
97
|
+
def keep_type(decl, others, nested_lead)
|
|
98
|
+
taken = GatedSignaturePlan.declared_keys(others)
|
|
99
|
+
own = decl.members.reject { |member| GatedSignaturePlan.type_declaration?(member) }
|
|
100
|
+
members = own.filter_map { |member| GatedSignaturePlan.untaken(member, taken) }
|
|
101
|
+
@changed ||= members.size != own.size || members.zip(own).any? { |kept, member| !kept.equal?(member) }
|
|
102
|
+
GatedSignaturePlan.rebuild(decl, members: members + nested_lead)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# A type no other source declares that still failed to build: its own members, no superclass, stamped as a
|
|
106
|
+
# synthesized stub type so what it no longer inherits reads untyped.
|
|
107
|
+
def open_type(decl, nested)
|
|
108
|
+
@changed = true
|
|
109
|
+
own = decl.members.reject { |member| GatedSignaturePlan.type_declaration?(member) }
|
|
110
|
+
GatedSignaturePlan.header_copy(decl, decl.name, own + nested, buffer_name: RbsLoader::SYNTHETIC_STUB_BUFFER)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
module_function
|
|
115
|
+
|
|
116
|
+
# @param roots — the gated directories (Pathnames) to plan, each loaded whole on the first attempt.
|
|
117
|
+
# @param base_env — the environment built without them: every other signature source, resolved.
|
|
118
|
+
# @param shelled — absolute-less type names to shell regardless of their headers.
|
|
119
|
+
def build(roots, base_env, shelled = Set.new)
|
|
120
|
+
planner = Planner.new(base_env, shelled)
|
|
121
|
+
leading = []
|
|
122
|
+
shells = []
|
|
123
|
+
signature_files(roots).each do |file|
|
|
124
|
+
buffer, directives, decls = RbsLoader.parse_signature_file(file)
|
|
125
|
+
next if buffer.nil?
|
|
126
|
+
|
|
127
|
+
lead, shell = planner.plan_decls(decls, nil)
|
|
128
|
+
leading << [buffer, directives, lead]
|
|
129
|
+
shells << [shell_buffer, directives, shell] unless shell.empty?
|
|
130
|
+
end
|
|
131
|
+
Plan.new(leading: leading, shells: shells, changed: planner.changed)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def signature_files(roots)
|
|
135
|
+
roots.flat_map { |root| Dir.glob(File.join(root.to_s, "*.rbs")).sort }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def shell_buffer
|
|
139
|
+
::RBS::Buffer.new(name: SHELL_BUFFER, content: "")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The declarations of `name` in `env` that some other source made, synthesized stubs left out.
|
|
143
|
+
def other_declarations(env, name)
|
|
144
|
+
entry = env&.class_decls&.[](::RBS::TypeName.parse("::#{name}"))
|
|
145
|
+
return [] if entry.nil?
|
|
146
|
+
|
|
147
|
+
RbsLoader.entry_declarations(entry).reject { |decl| synthetic?(decl) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def synthetic?(decl)
|
|
151
|
+
synthetic_buffers.include?(RbsLoader.declaration_buffer_name(decl))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# A class one side declares as a module, or a different number of type parameters. A disagreement the
|
|
155
|
+
# declarations do not show this plainly — a superclass, a variance, a bound — is the trial build's to find.
|
|
156
|
+
def header_conflict?(decl, others)
|
|
157
|
+
first = others.first
|
|
158
|
+
return false if first.nil?
|
|
159
|
+
return true unless first.instance_of?(decl.class)
|
|
160
|
+
|
|
161
|
+
first.type_params.size != decl.type_params.size
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def declared_keys(others)
|
|
165
|
+
others.each_with_object(Set.new) do |other, keys|
|
|
166
|
+
other.members.each { |member| RbsLoader.member_method_keys(member).each { |key| keys << key } }
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# `member` less the methods in `taken`: itself when it declares none of them, nil when it declares only
|
|
171
|
+
# them, else narrowed to the one it declares that is not taken.
|
|
172
|
+
def untaken(member, taken)
|
|
173
|
+
keys = RbsLoader.member_method_keys(member)
|
|
174
|
+
left = keys.reject { |key| taken.include?(key) }
|
|
175
|
+
return member if left.size == keys.size
|
|
176
|
+
return nil if left.empty?
|
|
177
|
+
|
|
178
|
+
narrowed(member, left.first)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# The two members that declare two methods: an `attr_accessor` (a reader and a writer) and a
|
|
182
|
+
# `def self?.m` (an instance and a singleton method). The instance side of a `self?` method is private
|
|
183
|
+
# (a module function), so narrowing to it keeps that.
|
|
184
|
+
def narrowed(member, key)
|
|
185
|
+
method_name, kind = key
|
|
186
|
+
if member.is_a?(::RBS::AST::Members::MethodDefinition)
|
|
187
|
+
return member.update(kind: kind, visibility: kind == :instance ? :private : member.visibility)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
attribute = method_name.to_s.end_with?("=") ? ::RBS::AST::Members::AttrWriter : ::RBS::AST::Members::AttrReader
|
|
191
|
+
attribute.new(
|
|
192
|
+
name: member.name, type: member.type, ivar_name: member.ivar_name, kind: member.kind,
|
|
193
|
+
annotations: member.annotations, location: member.location, comment: member.comment,
|
|
194
|
+
visibility: member.visibility
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# `source`'s kind and type parameters under `name`, carrying only `members` — no superclass, self types,
|
|
199
|
+
# annotations or members of its own, all of which a reopening declaration may omit.
|
|
200
|
+
def header_copy(source, name, members, buffer_name: SHELL_BUFFER)
|
|
201
|
+
location = ::RBS::Location.new(::RBS::Buffer.new(name: buffer_name, content: ""), 0, 0)
|
|
202
|
+
if source.is_a?(::RBS::AST::Declarations::Class)
|
|
203
|
+
::RBS::AST::Declarations::Class.new(
|
|
204
|
+
name: name, type_params: source.type_params, super_class: nil, members: members,
|
|
205
|
+
annotations: [], location: location, comment: nil
|
|
206
|
+
)
|
|
207
|
+
else
|
|
208
|
+
::RBS::AST::Declarations::Module.new(
|
|
209
|
+
name: name, type_params: source.type_params, members: members, self_types: [],
|
|
210
|
+
annotations: [], location: location, comment: nil
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def rebuild(decl, members:)
|
|
216
|
+
if decl.is_a?(::RBS::AST::Declarations::Class)
|
|
217
|
+
::RBS::AST::Declarations::Class.new(
|
|
218
|
+
name: decl.name, type_params: decl.type_params, super_class: decl.super_class, members: members,
|
|
219
|
+
annotations: decl.annotations, location: decl.location, comment: decl.comment
|
|
220
|
+
)
|
|
221
|
+
else
|
|
222
|
+
::RBS::AST::Declarations::Module.new(
|
|
223
|
+
name: decl.name, type_params: decl.type_params, members: members, self_types: decl.self_types,
|
|
224
|
+
annotations: decl.annotations, location: decl.location, comment: decl.comment
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def type_declaration?(decl)
|
|
230
|
+
decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def qualify(namespace, type_name)
|
|
234
|
+
text = type_name.to_s
|
|
235
|
+
return text.delete_prefix("::") if text.start_with?("::") || namespace.nil?
|
|
236
|
+
|
|
237
|
+
"#{namespace}::#{text}"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# What stood down from each of `roots` in `env`, as `[dir, entries, whole]`: `entries` are
|
|
241
|
+
# `[declaration, cause_file, reason]`, `declaration` reading `Integer#prime?`, `Integer.from_prime_division`
|
|
242
|
+
# or `Prime` (a whole type), `cause_file` the buffer name of the other source's declaration, or nil when it
|
|
243
|
+
# cannot be named, and `reason` one of `"member"` (another source declares that method), `"type"` (the
|
|
244
|
+
# type stood down whole, its header contradicted or failing beside another source's declaration) or
|
|
245
|
+
# `"open"` (it failed with no other source declaring it, and was kept without its superclass, unchecked).
|
|
246
|
+
# `whole` is true when the directory stood down entirely; its entries are then only the clashes the
|
|
247
|
+
# environment still shows. A directory nothing stood down from is left out.
|
|
248
|
+
#
|
|
249
|
+
# @param roots — `{dir_basename => Pathname}` of the gated directories the run activated.
|
|
250
|
+
# @param project_files — the project's own signature files: a cause is named from them (or from inline
|
|
251
|
+
# RBS) in preference to another source, and only from them when the type failed rather than clashed.
|
|
252
|
+
def standdowns(env, roots, project_files = Set.new)
|
|
253
|
+
return [] if env.nil?
|
|
254
|
+
|
|
255
|
+
loaded = RbsLoader.loaded_signature_buffers(env)&.to_set { |buffer| buffer.name.to_s } || Set.new
|
|
256
|
+
roots.sort.filter_map do |dir, root|
|
|
257
|
+
files = signature_files([root])
|
|
258
|
+
whole = files.none? { |file| loaded.include?(file) }
|
|
259
|
+
entries = files.flat_map { |file| file_standdowns(env, file, whole, project_files) }.uniq
|
|
260
|
+
[dir, entries, whole] if whole || !entries.empty?
|
|
261
|
+
end
|
|
262
|
+
rescue ::RBS::BaseError
|
|
263
|
+
[]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def file_standdowns(env, file, whole, project_files)
|
|
267
|
+
_buffer, _directives, decls = RbsLoader.parse_signature_file(file)
|
|
268
|
+
return [] if decls.nil?
|
|
269
|
+
|
|
270
|
+
entries = []
|
|
271
|
+
each_type(decls, nil) do |decl, name|
|
|
272
|
+
entries.concat(type_standdowns(env, file, [decl, name], whole, project_files))
|
|
273
|
+
end
|
|
274
|
+
entries
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def each_type(decls, namespace, &block)
|
|
278
|
+
decls.each do |decl|
|
|
279
|
+
next unless type_declaration?(decl)
|
|
280
|
+
|
|
281
|
+
name = qualify(namespace, decl.name)
|
|
282
|
+
block.call(decl, name)
|
|
283
|
+
each_type(decl.members, name, &block)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def type_standdowns(env, file, (decl, name), whole, project_files)
|
|
288
|
+
entry = env.class_decls[::RBS::TypeName.parse("::#{name}")]
|
|
289
|
+
declarations = entry.nil? ? [] : RbsLoader.entry_declarations(entry)
|
|
290
|
+
vendored = declarations.select { |other| RbsLoader.declaration_buffer_name(other) == file }
|
|
291
|
+
others = declarations.reject { |other| vendored.include?(other) || synthetic?(other) }
|
|
292
|
+
# A partial load shells a type that stood down, so its vendored declaration is gone; a whole stand-down
|
|
293
|
+
# removed every vendored declaration, and only a header the environment still contradicts names a type.
|
|
294
|
+
conflict = header_conflict?(decl, others)
|
|
295
|
+
type_row = [[name, type_cause(others, conflict, project_files), "type"]]
|
|
296
|
+
return conflict ? type_row : member_standdowns(decl, name, [], others, true) if whole
|
|
297
|
+
return member_standdowns(decl, name, vendored, others, false) unless vendored.empty?
|
|
298
|
+
return [[name, nil, "open"]] if others.empty? && declarations.any? { |other| stub?(other) }
|
|
299
|
+
|
|
300
|
+
type_row
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# A header clash names the declaration it clashed with, preferring the project's own; a type that only
|
|
304
|
+
# failed beside another source names a project or inline declaration of it, never core or a gem's copy.
|
|
305
|
+
def type_cause(others, conflict, project_files)
|
|
306
|
+
own = others.find do |other|
|
|
307
|
+
name = buffer_of(other).to_s
|
|
308
|
+
project_files.include?(name) || name.start_with?("virtual:")
|
|
309
|
+
end
|
|
310
|
+
buffer_of(own || (conflict ? others.first : nil))
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def stub?(decl)
|
|
314
|
+
RbsLoader.declaration_buffer_name(decl) == RbsLoader::SYNTHETIC_STUB_BUFFER
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def member_standdowns(decl, name, vendored, others, whole)
|
|
318
|
+
present = declared_keys(vendored)
|
|
319
|
+
decl.members.flat_map do |member|
|
|
320
|
+
RbsLoader.member_method_keys(member).reject { |key| present.include?(key) }.filter_map do |key|
|
|
321
|
+
cause = others.find { |other| other.members.any? { |m| RbsLoader.member_method_keys(m).include?(key) } }
|
|
322
|
+
next if whole && cause.nil?
|
|
323
|
+
|
|
324
|
+
[member_label(name, key), buffer_of(cause), "member"]
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def member_label(name, key)
|
|
330
|
+
method_name, kind = key
|
|
331
|
+
"#{name}#{kind == :singleton ? '.' : '#'}#{method_name}"
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def buffer_of(decl)
|
|
335
|
+
decl && RbsLoader.declaration_buffer_name(decl)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
@@ -35,11 +35,12 @@ module Rigor
|
|
|
35
35
|
found.freeze
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
# `<bundle>/ruby/X.Y.Z/gems/<name>-<version
|
|
38
|
+
# `<bundle>/ruby/X.Y.Z/gems/<name>-<version>[-<platform>]/` — the same pure-filesystem layout
|
|
39
39
|
# {MissingGemConstantIndex.bundle_gem_dirs} walks, preferred over the host's gems so a project that
|
|
40
40
|
# vendored its bundle without committing the lockfile is read from its own tree. A platform-tagged
|
|
41
|
-
# directory (`
|
|
42
|
-
#
|
|
41
|
+
# directory (`commonmarker-2.10.0-arm64-darwin`) is kept with its platform as a variant: those builds
|
|
42
|
+
# ship the same `lib/<name>.rb` and top-level namespace as the `ruby` one, and the index looks the
|
|
43
|
+
# directory up by that variant. A name installed more than once keeps every directory as a variant.
|
|
43
44
|
def from_bundle_tree(bundle_path)
|
|
44
45
|
return {} if bundle_path.nil?
|
|
45
46
|
|
|
@@ -47,10 +48,15 @@ module Rigor
|
|
|
47
48
|
return {} unless base.directory?
|
|
48
49
|
|
|
49
50
|
Dir.glob(base.join("ruby", "*", "gems", "*")).each_with_object({}) do |dir, acc|
|
|
50
|
-
name, version =
|
|
51
|
+
name, version, platform = split_install_dirname(File.basename(dir))
|
|
51
52
|
next unless name
|
|
52
53
|
|
|
53
|
-
acc[name]
|
|
54
|
+
prior = acc[name]
|
|
55
|
+
acc[name] = if prior
|
|
56
|
+
prior.with(variants: [*prior.variants, [version, platform]])
|
|
57
|
+
else
|
|
58
|
+
locked_gem(name, version, platform)
|
|
59
|
+
end
|
|
54
60
|
end
|
|
55
61
|
end
|
|
56
62
|
|
|
@@ -70,15 +76,36 @@ module Rigor
|
|
|
70
76
|
{}
|
|
71
77
|
end
|
|
72
78
|
|
|
73
|
-
def locked_gem(name, version)
|
|
74
|
-
LockfileResolver::LockedGem.new(name: name, version: version, platform:
|
|
79
|
+
def locked_gem(name, version, platform = "ruby")
|
|
80
|
+
LockfileResolver::LockedGem.new(name: name, version: version, platform: platform)
|
|
75
81
|
end
|
|
76
82
|
|
|
77
|
-
# Splits a gem install directory's basename
|
|
78
|
-
#
|
|
79
|
-
|
|
83
|
+
# Splits a gem install directory's basename into `[name, version, platform]`, or nil. The version
|
|
84
|
+
# always starts with a digit and carries no hyphen; a platform tail is accepted only when RubyGems
|
|
85
|
+
# recognises its OS (`arm64-darwin`, `x86_64-linux-gnu`, `java`), so `foo-2fa-1.0` stays the plain gem
|
|
86
|
+
# `foo-2fa`. The platform split is tried first, because a plain split of `foo-1.0-universal-darwin-23`
|
|
87
|
+
# would claim the name `foo-1.0-universal-darwin`; and from the rightmost version-shaped segment,
|
|
88
|
+
# because RubyGems also reads `1.0-x86_64-linux` as a linux platform, which would cut
|
|
89
|
+
# `foo-2fa-1.0-x86_64-linux` at `foo`.
|
|
90
|
+
def split_install_dirname(basename)
|
|
91
|
+
platform_split(basename) || plain_split(basename)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def platform_split(basename)
|
|
95
|
+
cuts = []
|
|
96
|
+
basename.scan(/-(\d[^-]*)(?=-.)/) { cuts << Regexp.last_match }
|
|
97
|
+
cuts.reverse_each do |match|
|
|
98
|
+
platform = match.post_match.delete_prefix("-")
|
|
99
|
+
# `current` makes `Gem::Platform.new` read the host's platform, which a non-main Ractor may not touch.
|
|
100
|
+
next if platform == "current"
|
|
101
|
+
return [match.pre_match, match[1], platform] unless Gem::Platform.new(platform).os == "unknown"
|
|
102
|
+
end
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def plain_split(basename)
|
|
80
107
|
match = /\A(.+)-(\d[^-]*)\z/.match(basename)
|
|
81
|
-
match ? [match[1], match[2]] : nil
|
|
108
|
+
match ? [match[1], match[2], "ruby"] : nil
|
|
82
109
|
end
|
|
83
110
|
end
|
|
84
111
|
end
|
|
@@ -30,13 +30,29 @@ module Rigor
|
|
|
30
30
|
# dependency that moved from a `git:` source to a released version without a `bundle clean`, so its
|
|
31
31
|
# git-installed directory is still sitting there under the OLD source's name). Defaults `false` so
|
|
32
32
|
# existing callers that construct a `LockedGem` without an opinion stay conservative.
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
#
|
|
34
|
+
# `variants` is every `[version, platform]` pair the lockfile locks under this name, in Bundler's parsed
|
|
35
|
+
# order. A multi-platform lockfile (`bundle lock --add-platform`) lists one spec per platform — `ffi
|
|
36
|
+
# (1.17.4)`, `ffi (1.17.4-arm64-darwin)`, … — and Bundler installs whichever matches the host, so
|
|
37
|
+
# {BundleSigDiscovery} must admit all of them (#1844). `version` / `platform` name one representative
|
|
38
|
+
# variant for the readers that want a single version (coverage report, doctor): the `ruby` one when the
|
|
39
|
+
# lockfile has it, otherwise the last parsed. Defaults to the single `[version, platform]` pair, and the
|
|
40
|
+
# pair must be one of `variants`.
|
|
41
|
+
LockedGem = Data.define(:name, :version, :platform, :git_source, :variants) do
|
|
42
|
+
def initialize(name:, version:, platform:, git_source: false, variants: nil)
|
|
43
|
+
version = -version.to_s
|
|
44
|
+
platform = -platform.to_s
|
|
45
|
+
variants = (variants || [[version, platform]]).map { |v, p| [-v.to_s, -p.to_s].freeze }.uniq.freeze
|
|
46
|
+
unless variants.include?([version, platform])
|
|
47
|
+
raise ArgumentError, "LockedGem #{name}: [#{version}, #{platform}] is not one of its variants"
|
|
48
|
+
end
|
|
49
|
+
|
|
35
50
|
super(
|
|
36
51
|
name: -name.to_s,
|
|
37
|
-
version:
|
|
38
|
-
platform:
|
|
39
|
-
git_source: git_source ? true : false
|
|
52
|
+
version: version,
|
|
53
|
+
platform: platform,
|
|
54
|
+
git_source: git_source ? true : false,
|
|
55
|
+
variants: variants
|
|
40
56
|
)
|
|
41
57
|
end
|
|
42
58
|
end
|
|
@@ -162,10 +178,13 @@ module Rigor
|
|
|
162
178
|
# `#source` is the `Bundler::Source::*` this spec resolved from — `Git` for a `GIT` lockfile
|
|
163
179
|
# stanza, `Rubygems` for a `GEM` one, `Path`/`Gemspec` for `path:`. Only `Git` sources land under
|
|
164
180
|
# {BundleSigDiscovery}'s `bundler/gems/` walk.
|
|
165
|
-
|
|
181
|
+
# A multi-platform lockfile repeats the name once per platform; fold the repeats into `variants`
|
|
182
|
+
# instead of letting the last one overwrite the rest (#1844).
|
|
183
|
+
entry = LockedGem.new(
|
|
166
184
|
name: spec.name, version: spec.version.to_s, platform: platform,
|
|
167
185
|
git_source: spec.source.is_a?(Bundler::Source::Git)
|
|
168
186
|
)
|
|
187
|
+
h[entry.name] = (prior = h[entry.name]) ? merge_variant(prior, entry) : entry
|
|
169
188
|
end
|
|
170
189
|
locked.freeze
|
|
171
190
|
rescue ::Ractor::IsolationError => e
|
|
@@ -177,6 +196,18 @@ module Rigor
|
|
|
177
196
|
end
|
|
178
197
|
private_class_method :do_parse
|
|
179
198
|
|
|
199
|
+
# Folds a further platform variant of an already-parsed name into it. The representative keeps a `ruby`
|
|
200
|
+
# variant once seen; otherwise the last variant wins, as it did before variants were kept.
|
|
201
|
+
def self.merge_variant(prior, entry)
|
|
202
|
+
rep = prior.platform == "ruby" ? prior : entry
|
|
203
|
+
prior.with(
|
|
204
|
+
variants: [*prior.variants, [entry.version, entry.platform]],
|
|
205
|
+
version: rep.version, platform: rep.platform,
|
|
206
|
+
git_source: prior.git_source || entry.git_source
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
private_class_method :merge_variant
|
|
210
|
+
|
|
180
211
|
# #1064 — Bundler's lockfile parser reads RubyGems module state a non-main Ractor may not touch, so the
|
|
181
212
|
# parse raises `Ractor::IsolationError` there whatever the lockfile says. That is an engine constraint, not
|
|
182
213
|
# a malformed lockfile, and the warning must not send the user to fix a file that is fine. The Ractor pool
|
|
@@ -37,8 +37,8 @@ module Rigor
|
|
|
37
37
|
module MissingGemConstantIndex
|
|
38
38
|
module_function
|
|
39
39
|
|
|
40
|
-
# @param gems — `[gem_name, version]`
|
|
41
|
-
#
|
|
40
|
+
# @param gems — `[gem_name, version, variants]` entries (the `:missing` rows of {RbsCoverageReport},
|
|
41
|
+
# with the gem's `LockedGem#variants`); `variants` may be omitted or nil, meaning the `ruby` one.
|
|
42
42
|
# @param bundle_path — the target's resolved bundler install root, or nil.
|
|
43
43
|
# @param spec_resolver — `(name, version) -> String?` fallback dir resolver. Injectable for
|
|
44
44
|
# specs; the default is the RubyGems-metadata lookup (no code load).
|
|
@@ -49,8 +49,9 @@ module Rigor
|
|
|
49
49
|
bundle_dirs = bundle_gem_dirs(bundle_path)
|
|
50
50
|
git_dirs = git_gem_dirs(bundle_path)
|
|
51
51
|
index = {}
|
|
52
|
-
gems.each do |gem_name, version|
|
|
53
|
-
dir = bundle_dirs
|
|
52
|
+
gems.each do |gem_name, version, variants|
|
|
53
|
+
dir = bundle_dir(bundle_dirs, gem_name, version, variants) ||
|
|
54
|
+
git_dirs[gem_name] || spec_resolver.call(gem_name, version)
|
|
54
55
|
next unless dir
|
|
55
56
|
|
|
56
57
|
entry_files(dir, gem_name).each do |file|
|
|
@@ -62,9 +63,8 @@ module Rigor
|
|
|
62
63
|
|
|
63
64
|
# `{"<name>-<version>" => gem_dir}` for the target's bundle, or `{}` when no bundle is resolvable. The
|
|
64
65
|
# glob covers the RUBYGEMS-sourced layout only, `<bundle>/ruby/X.Y.Z/gems/<name>-<version>/`; the
|
|
65
|
-
# git-sourced layout is {.git_gem_dirs}'s. Keyed on the dir basename
|
|
66
|
-
#
|
|
67
|
-
# native code, not the pure-Ruby constants this indexes.
|
|
66
|
+
# git-sourced layout is {.git_gem_dirs}'s. Keyed on the dir basename, which {.bundle_dir} reconstructs
|
|
67
|
+
# per locked variant.
|
|
68
68
|
def bundle_gem_dirs(bundle_path)
|
|
69
69
|
return {} if bundle_path.nil?
|
|
70
70
|
|
|
@@ -76,6 +76,21 @@ module Rigor
|
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
# The installed directory of one of the gem's locked variants: `<name>-<version>` for the `ruby` one,
|
|
80
|
+
# `<name>-<version>-<platform>` for a platform-tagged one. Bundler installs only the variant matching
|
|
81
|
+
# the host, and a multi-platform lockfile's representative is usually the `ruby` one, so every variant
|
|
82
|
+
# is tried. A platform-tagged gem is not a native-only gem: the commonmarker, nokogiri and sqlite3 builds
|
|
83
|
+
# each ship `lib/<name>.rb` declaring the gem's top-level namespace, exactly as the `ruby` build does.
|
|
84
|
+
def bundle_dir(bundle_dirs, gem_name, version, variants)
|
|
85
|
+
[[version, "ruby"], *variants].each do |variant_version, platform|
|
|
86
|
+
basename = "#{gem_name}-#{variant_version}"
|
|
87
|
+
basename = "#{basename}-#{platform}" unless platform == "ruby"
|
|
88
|
+
dir = bundle_dirs[basename]
|
|
89
|
+
return dir if dir
|
|
90
|
+
end
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
79
94
|
# Issue #763 — `{gem_name => gem_dir}` for the target's GIT-sourced gems,
|
|
80
95
|
# `<bundle>/ruby/X.Y.Z/bundler/gems/<repo>-<12-hex-revision>/`. Before this, a `git:`-sourced gem was
|
|
81
96
|
# invisible to the index, so every constant reaching into it recorded the generic engine-gap cause
|
|
@@ -32,7 +32,7 @@ module Rigor
|
|
|
32
32
|
# hard-coding is acceptable; a directory walk at every call would add stat-cost to no benefit.)
|
|
33
33
|
VENDORED_GEM_NAMES = Set[
|
|
34
34
|
"ast", "bcrypt", "bundler", "cgi", "did_you_mean",
|
|
35
|
-
"idn-ruby", "mysql2", "nokogiri", "pg", "prism", "racc", "redis", "rubygems"
|
|
35
|
+
"idn-ruby", "mysql2", "nokogiri", "pg", "prime", "prism", "racc", "redis", "rubygems"
|
|
36
36
|
].freeze
|
|
37
37
|
|
|
38
38
|
# @param locked_gems — The lockfile-resolved gem set. Empty
|