rigortype 0.4.1 → 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 +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 +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/03-configuration.md +1 -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/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 +21 -2
- data/lib/rigor/analysis/check_rules.rb +289 -57
- 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/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 +3 -1
- data/lib/rigor/analysis/incremental_session.rb +14 -1
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
- data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +34 -3
- data/lib/rigor/analysis/worker_session.rb +7 -4
- data/lib/rigor/cache/incremental_snapshot.rb +22 -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 +2 -1
- 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 +13 -7
- data/lib/rigor/cli.rb +10 -2
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +46 -2
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +283 -14
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment.rb +21 -3
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +215 -16
- 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 +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 +59 -12
- 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 +259 -28
- 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 +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_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/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +687 -60
- data/lib/rigor/inference/statement_evaluator.rb +71 -20
- 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/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 +29 -6
- data/lib/rigor/protection/mutation_cache.rb +1 -1
- 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 +189 -10
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +55 -5
- 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-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 +27 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- metadata +66 -1
|
@@ -51,7 +51,7 @@ module Rigor
|
|
|
51
51
|
paths = @argv.empty? ? configuration.paths : @argv
|
|
52
52
|
scan_cache = Analysis::Reachability::ScanCache.open(configuration.cache_path,
|
|
53
53
|
target_ruby: configuration.target_ruby)
|
|
54
|
-
declarations, references, dynamic_uses = scan(paths, configuration, scan_cache)
|
|
54
|
+
declarations, references, dynamic_uses, shadows = scan(paths, configuration, scan_cache)
|
|
55
55
|
references.concat(signature_references(configuration))
|
|
56
56
|
dynamic_uses.concat(template_mentions(declarations, scan_cache))
|
|
57
57
|
|
|
@@ -59,7 +59,8 @@ module Rigor
|
|
|
59
59
|
cache_store: cache_store(configuration))
|
|
60
60
|
references.concat(plugin_references(contribution.references))
|
|
61
61
|
graph = build_graph(configuration, options, contribution,
|
|
62
|
-
declarations: declarations, references: references, dynamic_uses: dynamic_uses
|
|
62
|
+
declarations: declarations, references: references, dynamic_uses: dynamic_uses,
|
|
63
|
+
shadows: shadows)
|
|
63
64
|
emit(graph.report, options, supply: root_supply(contribution.roots, declarations))
|
|
64
65
|
scan_cache.save
|
|
65
66
|
0
|
|
@@ -67,9 +68,9 @@ module Rigor
|
|
|
67
68
|
|
|
68
69
|
private
|
|
69
70
|
|
|
70
|
-
def build_graph(configuration, options, contribution, declarations:, references:, dynamic_uses:)
|
|
71
|
+
def build_graph(configuration, options, contribution, declarations:, references:, dynamic_uses:, shadows:)
|
|
71
72
|
Analysis::Reachability::Graph.new(
|
|
72
|
-
declarations: declarations, references: references, dynamic_uses: dynamic_uses,
|
|
73
|
+
declarations: declarations, references: references, dynamic_uses: dynamic_uses, shadows: shadows,
|
|
73
74
|
root_fqns: root_fqns(declarations, options.fetch(:entry_points)) + contribution.roots,
|
|
74
75
|
foreign: foreign_predicate(configuration)
|
|
75
76
|
)
|
|
@@ -106,22 +107,25 @@ module Rigor
|
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
# Declarations come from the analysed paths; references additionally from the wider corpus (WD7).
|
|
110
|
+
# A wider-corpus file's own declarations come back as shadows: never nodes, only what that file's own
|
|
111
|
+
# references resolve to first (#1732).
|
|
109
112
|
# Every file is consulted every run — an unchanged one contributes its cached scan, so the
|
|
110
113
|
# whole-project completeness the `--incremental` refusal protects is unaffected.
|
|
111
114
|
def scan(paths, configuration, cache)
|
|
112
115
|
declaration_files = Analysis::PathExpansion.ruby_files(paths, configuration.exclude_patterns).to_set
|
|
113
116
|
declarations = []
|
|
117
|
+
shadows = []
|
|
114
118
|
references = []
|
|
115
119
|
dynamic_uses = []
|
|
116
120
|
(declaration_files + reference_files(paths, configuration)).sort.each do |file|
|
|
117
121
|
result = cache.serve(:scan, file) { read_and_scan(file, configuration) }
|
|
118
122
|
next if result.nil?
|
|
119
123
|
|
|
120
|
-
declarations.concat(result.declarations)
|
|
124
|
+
(declaration_files.include?(file) ? declarations : shadows).concat(result.declarations)
|
|
121
125
|
references.concat(result.references)
|
|
122
126
|
dynamic_uses.concat(result.dynamic_uses)
|
|
123
127
|
end
|
|
124
|
-
[declarations, references, dynamic_uses]
|
|
128
|
+
[declarations, references, dynamic_uses, shadows]
|
|
125
129
|
end
|
|
126
130
|
|
|
127
131
|
# The reference corpus is the PROJECT, not the analysed paths. A constant declared in `lib/` is commonly
|
|
@@ -137,7 +141,8 @@ module Rigor
|
|
|
137
141
|
|
|
138
142
|
def read_and_scan(file, configuration)
|
|
139
143
|
Analysis::Reachability::Scan.call(path: file, source: File.read(file),
|
|
140
|
-
target_ruby: configuration.target_ruby
|
|
144
|
+
target_ruby: configuration.target_ruby,
|
|
145
|
+
root: File.absolute_path(Dir.pwd))
|
|
141
146
|
rescue SystemCallError
|
|
142
147
|
nil
|
|
143
148
|
end
|
|
@@ -235,6 +240,7 @@ module Rigor
|
|
|
235
240
|
def foreign_predicate(configuration)
|
|
236
241
|
env = Environment.for_project(libraries: configuration.libraries, signature_paths: [],
|
|
237
242
|
cache_store: cache_store(configuration),
|
|
243
|
+
required_features: ProjectEnvironment.required_features(configuration),
|
|
238
244
|
**ProjectEnvironment.dependency_discovery_options(configuration))
|
|
239
245
|
->(fqn) { !env.singleton_for_name(fqn).nil? }
|
|
240
246
|
rescue StandardError
|
data/lib/rigor/cli.rb
CHANGED
|
@@ -281,6 +281,10 @@ module Rigor
|
|
|
281
281
|
# tempfile, uri, logger, date, prism, rbs). Adding a
|
|
282
282
|
# `sig/<gem>.rbs` file under `sig/` is the simplest way
|
|
283
283
|
# to extend type coverage today.
|
|
284
|
+
#
|
|
285
|
+
# `target_ruby` is left commented: the default only picks the syntax Rigor parses. Set it to the lowest
|
|
286
|
+
# Ruby your project runs on, and Rigor also reports what that Ruby deprecates.
|
|
287
|
+
# target_ruby: #{Configuration::DEFAULTS.fetch('target_ruby').dump}
|
|
284
288
|
#{YAML.dump(init_settings(path)).sub(/\A---\n/, '')}
|
|
285
289
|
YAML
|
|
286
290
|
end
|
|
@@ -288,13 +292,17 @@ module Rigor
|
|
|
288
292
|
# The settings `rigor init` writes: the defaults, with `test_paths:` spelled out as the test roots found now, so
|
|
289
293
|
# the project's config says where its tests are rather than leaving it to auto-detection. The roots are written
|
|
290
294
|
# relative to the config file's directory, which is what {Configuration.load} resolves them against.
|
|
295
|
+
#
|
|
296
|
+
# `target_ruby` is left out (the template writes it commented): a written key is a statement about the runtime
|
|
297
|
+
# (#1692), and the starter must not make one the user never chose.
|
|
291
298
|
def init_settings(path)
|
|
299
|
+
settings = Configuration::DEFAULTS.except("target_ruby")
|
|
292
300
|
test_paths = Configuration.new.resolved_test_paths
|
|
293
|
-
return
|
|
301
|
+
return settings if test_paths.empty?
|
|
294
302
|
|
|
295
303
|
config_dir = Pathname(File.absolute_path(File.dirname(path)))
|
|
296
304
|
relative = test_paths.map { |root| Pathname(root).relative_path_from(config_dir).to_s }
|
|
297
|
-
|
|
305
|
+
settings.merge("test_paths" => relative)
|
|
298
306
|
end
|
|
299
307
|
|
|
300
308
|
def run_annotate
|
|
@@ -38,6 +38,10 @@ module Rigor
|
|
|
38
38
|
"call.argument-type-mismatch" => :warning,
|
|
39
39
|
"call.possible-nil-receiver" => :warning,
|
|
40
40
|
"call.raise-non-exception" => :warning,
|
|
41
|
+
# Issue #1692 — the code runs on the stated Ruby (it warns, and breaks only at the removal version), so the
|
|
42
|
+
# rule takes the slot of the rules that report working-but-wrong code: `flow.unreachable-branch`'s
|
|
43
|
+
# info / warning / error. It runs only under an explicit `target_ruby` of 4.1 or later.
|
|
44
|
+
"call.deprecated-ruby2-keywords" => :info,
|
|
41
45
|
"flow.always-raises" => :warning,
|
|
42
46
|
"flow.unreachable-branch" => :info,
|
|
43
47
|
"flow.dead-assignment" => :info,
|
|
@@ -100,6 +104,7 @@ module Rigor
|
|
|
100
104
|
"call.argument-type-mismatch" => :error,
|
|
101
105
|
"call.possible-nil-receiver" => :error,
|
|
102
106
|
"call.raise-non-exception" => :error,
|
|
107
|
+
"call.deprecated-ruby2-keywords" => :warning,
|
|
103
108
|
"flow.always-raises" => :error,
|
|
104
109
|
"flow.unreachable-branch" => :warning,
|
|
105
110
|
"flow.dead-assignment" => :warning,
|
|
@@ -141,6 +146,7 @@ module Rigor
|
|
|
141
146
|
"call.argument-type-mismatch" => :error,
|
|
142
147
|
"call.possible-nil-receiver" => :error,
|
|
143
148
|
"call.raise-non-exception" => :error,
|
|
149
|
+
"call.deprecated-ruby2-keywords" => :error,
|
|
144
150
|
"flow.always-raises" => :error,
|
|
145
151
|
"flow.unreachable-branch" => :error,
|
|
146
152
|
"flow.dead-assignment" => :error,
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -283,6 +283,23 @@ module Rigor
|
|
|
283
283
|
:effects_snapshot_path, :effects_snapshot_reach, :effects_snapshot_gate, :effects_tolerated,
|
|
284
284
|
:effects_labels, :effects_attribution, :effects_envelopes
|
|
285
285
|
|
|
286
|
+
# ADR-47 WD5 amendment (#1692) — whether `.rigor.yml` (or its `includes:` chain) set `target_ruby:` itself.
|
|
287
|
+
# The default `"4.0"` is a parse version, not something the user said about the Ruby the project runs on.
|
|
288
|
+
def target_ruby_explicit?
|
|
289
|
+
@target_ruby_explicit
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# ADR-47 WD5 amendment (#1692) — `target_ruby` read as a user statement about the runtime: the configured
|
|
293
|
+
# version when the user set it explicitly, nil when it is the default or `"latest"`. `"latest"` names the
|
|
294
|
+
# newest syntax this Prism build parses, which moves with a Rigor upgrade, so it states no runtime. Only the
|
|
295
|
+
# Ruby-deprecation rules read this; `Inference::VersionGuard` keeps the analyzer's own `RUBY_VERSION`.
|
|
296
|
+
def stated_runtime_ruby
|
|
297
|
+
return nil unless target_ruby_explicit?
|
|
298
|
+
return nil if target_ruby == "latest"
|
|
299
|
+
|
|
300
|
+
target_ruby
|
|
301
|
+
end
|
|
302
|
+
|
|
286
303
|
# ADR-103 WD13 — whether effect collection runs. True exactly when the loaded configuration carried an
|
|
287
304
|
# `effects:` block, whatever its body; `rigor effects` enables it for its own run by loading an
|
|
288
305
|
# implicit `effects: {}` instead of by consulting anything else. Nothing else — no annotation, no
|
|
@@ -327,6 +344,7 @@ module Rigor
|
|
|
327
344
|
if resolved.nil? || !File.exist?(resolved)
|
|
328
345
|
data = DEFAULTS
|
|
329
346
|
effects_key_present = false
|
|
347
|
+
target_ruby_key_present = false
|
|
330
348
|
else
|
|
331
349
|
# ADR-103 WD15 — captured from the RAW, pre-`DEFAULTS.merge` file (+ its `includes:` chain) because
|
|
332
350
|
# `DEFAULTS` itself carries an `"effects" => false` entry: once merged, "the file never wrote
|
|
@@ -334,9 +352,10 @@ module Rigor
|
|
|
334
352
|
# indistinguishable to {#initialize}. This is the only place that distinction still exists.
|
|
335
353
|
raw = load_with_includes(resolved)
|
|
336
354
|
effects_key_present = raw.key?("effects")
|
|
355
|
+
target_ruby_key_present = raw.key?("target_ruby")
|
|
337
356
|
data = DEFAULTS.merge(raw)
|
|
338
357
|
end
|
|
339
|
-
new(autowire_default_plugins(data), effects_key_present)
|
|
358
|
+
new(autowire_default_plugins(data), effects_key_present, target_ruby_key_present || false)
|
|
340
359
|
end
|
|
341
360
|
|
|
342
361
|
# ADR-93 WD2 — the one bundled plugin default-wired without a `plugins:` entry. `rigor-rbs-inline` is the
|
|
@@ -519,7 +538,13 @@ module Rigor
|
|
|
519
538
|
# `"key" => value` hash literal — this class's usual call shape, all over the spec suite — is coerced
|
|
520
539
|
# into keyword arguments by Ruby whenever the method declares ANY keyword parameter, which would break
|
|
521
540
|
# every `Configuration.new("some_key" => value)` call site with an "unknown keyword" `ArgumentError`.
|
|
522
|
-
|
|
541
|
+
# @param target_ruby_key_present — ADR-47 WD5 amendment (#1692) — whether the file set `target_ruby:` itself,
|
|
542
|
+
# captured before `DEFAULTS.merge` for the same reason as `effects_key_present` and positional for the same
|
|
543
|
+
# reason. See {#stated_runtime_ruby}. The default reads the key off `data`, which is right only for a hash that
|
|
544
|
+
# has not been through `DEFAULTS.merge` (DEFAULTS carries the key); a caller that merges passes it explicitly.
|
|
545
|
+
# The bare `Configuration.new` is not explicit.
|
|
546
|
+
def initialize(data = DEFAULTS, effects_key_present = data.key?("effects"),
|
|
547
|
+
target_ruby_key_present = data.key?("target_ruby") && !data.equal?(DEFAULTS))
|
|
523
548
|
# Record before the per-key fetches below discard the evidence. Top level only, deliberately —
|
|
524
549
|
# see {ConfigAudit.unknown_key_warnings} for why a nested check cannot key on DEFAULTS.
|
|
525
550
|
@unknown_keys = (data.keys.map(&:to_s) - KNOWN_KEYS).sort.freeze
|
|
@@ -528,6 +553,7 @@ module Rigor
|
|
|
528
553
|
plugins_io = DEFAULTS.fetch("plugins_io").merge(data.fetch("plugins_io", {}))
|
|
529
554
|
|
|
530
555
|
@target_ruby = coerce_target_ruby(data.fetch("target_ruby", DEFAULTS.fetch("target_ruby")))
|
|
556
|
+
@target_ruby_explicit = target_ruby_key_present ? true : false
|
|
531
557
|
@paths = Array(data.fetch("paths", DEFAULTS.fetch("paths"))).map(&:to_s).freeze
|
|
532
558
|
user_excludes = Array(data.fetch("exclude", DEFAULTS.fetch("exclude"))).map(&:to_s)
|
|
533
559
|
@exclude_patterns = (BUILTIN_EXCLUDES + user_excludes).uniq.freeze
|
|
@@ -619,6 +645,9 @@ module Rigor
|
|
|
619
645
|
def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
620
646
|
{
|
|
621
647
|
"target_ruby" => target_ruby,
|
|
648
|
+
# A stated `target_ruby` turns on the deprecation rules a defaulted one leaves off (#1692), so the run cache
|
|
649
|
+
# key and the incremental fingerprint must tell the two apart even where the value matches.
|
|
650
|
+
"target_ruby_explicit" => target_ruby_explicit?,
|
|
622
651
|
"paths" => paths,
|
|
623
652
|
"exclude" => exclude_patterns - BUILTIN_EXCLUDES,
|
|
624
653
|
"plugins" => plugins,
|
|
@@ -717,6 +746,21 @@ module Rigor
|
|
|
717
746
|
copy.freeze
|
|
718
747
|
end
|
|
719
748
|
|
|
749
|
+
# #1777 — a sibling Configuration with only `baseline:` unset, every other field (including the
|
|
750
|
+
# explicitness of `target_ruby`) shared with the receiver. `rigor baseline generate` analyses with
|
|
751
|
+
# this so the generating run sees exactly the configuration `rigor check` will, minus the filter it
|
|
752
|
+
# is about to replace; rebuilding from a key list let each unlisted key revert to its default.
|
|
753
|
+
#
|
|
754
|
+
# Same frozen `dup` shape as {#with_bleeding_edge}. `@baseline_path` is the only ivar
|
|
755
|
+
# `#initialize` derives from the `baseline:` key.
|
|
756
|
+
def without_baseline
|
|
757
|
+
return self if baseline_path.nil?
|
|
758
|
+
|
|
759
|
+
copy = dup
|
|
760
|
+
copy.instance_variable_set(:@baseline_path, nil)
|
|
761
|
+
copy.freeze
|
|
762
|
+
end
|
|
763
|
+
|
|
720
764
|
# ADR-103 WD14 — the ad-hoc opt-in `rigor effects` uses when the project's configuration carries no
|
|
721
765
|
# `effects:` block: a sibling Configuration with an implicit empty block, every other field shared.
|
|
722
766
|
# A configuration that already enables effects is returned unchanged, so a project's own settings
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
class Environment
|
|
7
|
+
# Issue #1700 — the type names a {RequiredFeatures}-gated vendored directory declares, which
|
|
8
|
+
# `RbsLoader.gated_trouble` builds in its trial to decide whether the directory may stay in an environment.
|
|
9
|
+
# Read off the vendored files themselves: a nested `class Prime; class EratosthenesGenerator` names
|
|
10
|
+
# `Prime::EratosthenesGenerator`, and a reopened core class (`class Integer`) is named too, since a clash
|
|
11
|
+
# there fails `Integer`'s own definition.
|
|
12
|
+
module GatedSignatureGuard
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# @param dir — the vendored directory (Pathname).
|
|
16
|
+
# @return the absolute-less names (`"Prime::PseudoPrimeGenerator"`) of every class and module it declares.
|
|
17
|
+
def type_names(dir)
|
|
18
|
+
Dir.glob(File.join(dir.to_s, "*.rbs")).flat_map do |file|
|
|
19
|
+
_, _, decls = RBS::Parser.parse_signature(File.read(file))
|
|
20
|
+
names(decls, nil)
|
|
21
|
+
rescue RBS::BaseError, SystemCallError
|
|
22
|
+
[]
|
|
23
|
+
end.uniq
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def names(decls, namespace)
|
|
27
|
+
decls.flat_map do |decl|
|
|
28
|
+
next [] unless decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)
|
|
29
|
+
|
|
30
|
+
name = qualify(namespace, decl.name)
|
|
31
|
+
[name] + names(decl.members, name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def qualify(namespace, type_name)
|
|
36
|
+
text = type_name.to_s
|
|
37
|
+
return text.delete_prefix("::") if text.start_with?("::") || namespace.nil?
|
|
38
|
+
|
|
39
|
+
"#{namespace}::#{text}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -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
|
|
@@ -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
|