rigortype 0.3.6 → 0.3.7
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/date.yml +6 -3
- data/data/builtins/ruby_core/enumerable.yml +1 -0
- data/data/builtins/ruby_core/exception.yml +2 -1
- data/data/builtins/ruby_core/file.yml +28 -14
- data/data/builtins/ruby_core/hash.yml +5 -2
- data/data/builtins/ruby_core/io.yml +33 -16
- data/data/builtins/ruby_core/random.yml +2 -1
- data/data/builtins/ruby_core/re.yml +6 -3
- data/data/builtins/ruby_core/struct.yml +2 -0
- data/data/builtins/ruby_core/time.yml +2 -1
- data/data/gem_overlay/activesupport/core_ext.rbs +409 -11
- data/docs/manual/02-cli-reference.md +16 -7
- data/docs/manual/05-inspecting-types.md +20 -8
- data/docs/manual/07-plugins.md +4 -0
- data/docs/manual/plugins/rigor-actionpack.md +61 -0
- data/docs/manual/plugins/rigor-activerecord.md +74 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
- data/docs/manual/plugins/rigor-sidekiq.md +22 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
- data/lib/rigor/analysis/check_rules.rb +383 -48
- data/lib/rigor/analysis/crash_signature.rb +116 -0
- data/lib/rigor/analysis/dependency_recorder.rb +18 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +28 -0
- data/lib/rigor/analysis/incremental_session.rb +44 -9
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
- data/lib/rigor/analysis/reachability/graph.rb +14 -5
- data/lib/rigor/analysis/reachability/scan.rb +7 -3
- data/lib/rigor/analysis/result.rb +32 -0
- data/lib/rigor/analysis/run_cache_key.rb +41 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
- data/lib/rigor/analysis/runner.rb +147 -10
- data/lib/rigor/analysis/worker_session.rb +10 -1
- data/lib/rigor/bleeding_edge.rb +7 -6
- data/lib/rigor/builtins/hkt_builtins.rb +45 -6
- data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
- data/lib/rigor/builtins/static_return_refinements.rb +20 -1
- data/lib/rigor/cache/descriptor.rb +42 -2
- data/lib/rigor/cache/incremental_snapshot.rb +40 -3
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
- data/lib/rigor/cache/store.rb +10 -1
- data/lib/rigor/cli/coverage_command.rb +23 -18
- data/lib/rigor/cli/coverage_mutation.rb +13 -17
- data/lib/rigor/cli/coverage_scan.rb +47 -6
- data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
- data/lib/rigor/cli/fused_protection_report.rb +23 -2
- data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
- data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
- data/lib/rigor/cli/mutation_protection_report.rb +31 -2
- data/lib/rigor/cli/sig_gen_command.rb +18 -0
- data/lib/rigor/cli/type_of_command.rb +188 -41
- data/lib/rigor/cli/type_of_renderer.rb +72 -3
- data/lib/rigor/cli/type_scan_command.rb +10 -1
- data/lib/rigor/cli.rb +1 -1
- data/lib/rigor/configuration.rb +7 -4
- data/lib/rigor/effects/plugin_facts.rb +1 -1
- data/lib/rigor/effects/scanner.rb +2 -3
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
- data/lib/rigor/environment/lockfile_resolver.rb +15 -5
- data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
- data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
- data/lib/rigor/environment/rbs_loader.rb +525 -46
- data/lib/rigor/environment.rb +38 -15
- data/lib/rigor/inference/acceptance.rb +172 -20
- data/lib/rigor/inference/captured_locals.rb +62 -0
- data/lib/rigor/inference/content_join.rb +347 -0
- data/lib/rigor/inference/def_handle.rb +15 -3
- data/lib/rigor/inference/def_node_resolver.rb +62 -3
- data/lib/rigor/inference/expression_typer.rb +1020 -141
- data/lib/rigor/inference/fork_map.rb +6 -1
- data/lib/rigor/inference/hkt_reducer.rb +12 -1
- data/lib/rigor/inference/hkt_registry.rb +46 -0
- data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
- data/lib/rigor/inference/index_write_widening.rb +48 -0
- data/lib/rigor/inference/indexed_narrowing.rb +14 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
- data/lib/rigor/inference/method_dispatcher.rb +155 -14
- data/lib/rigor/inference/method_parameter_binder.rb +3 -3
- data/lib/rigor/inference/mutation_widening.rb +244 -174
- data/lib/rigor/inference/narrowing.rb +331 -27
- data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
- data/lib/rigor/inference/pre_eval_constants.rb +15 -0
- data/lib/rigor/inference/precision_scanner.rb +5 -2
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +89 -39
- data/lib/rigor/inference/scope_indexer.rb +1493 -140
- data/lib/rigor/inference/statement_evaluator.rb +276 -91
- data/lib/rigor/inference/struct_fold_safety.rb +186 -27
- data/lib/rigor/inference/version_guard.rb +229 -0
- data/lib/rigor/language_server/selection_range_provider.rb +1 -1
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/inflector.rb +14 -5
- data/lib/rigor/plugin/io_boundary.rb +107 -4
- data/lib/rigor/plugin/loader.rb +14 -0
- data/lib/rigor/protection/analysis_guard.rb +60 -0
- data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
- data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
- data/lib/rigor/protection/discovery_seed.rb +1 -1
- data/lib/rigor/protection/measurement_integrity.rb +36 -0
- data/lib/rigor/protection/mutation_scanner.rb +61 -6
- data/lib/rigor/protection/mutator.rb +121 -18
- data/lib/rigor/reflection.rb +182 -30
- data/lib/rigor/scope/discovery_index.rb +39 -1
- data/lib/rigor/scope.rb +385 -4
- data/lib/rigor/sig_gen/classification.rb +12 -1
- data/lib/rigor/sig_gen/generator.rb +254 -5
- data/lib/rigor/sig_gen/renderer.rb +1 -6
- data/lib/rigor/sig_gen/writer.rb +3 -0
- data/lib/rigor/source/constant_path.rb +79 -2
- data/lib/rigor/triage/catalogue.rb +1 -1
- data/lib/rigor/type/combinator.rb +10 -0
- data/lib/rigor/type/maybe.rb +47 -0
- data/lib/rigor/type/refined.rb +1 -2
- data/lib/rigor/type/result.rb +53 -0
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
- data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
- data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
- data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
- data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
- data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
- data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
- data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
- data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
- data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
- data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
- data/sig/rigor/analysis/baseline.rbs +1 -1
- data/sig/rigor/environment.rbs +3 -0
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/io_boundary.rbs +2 -0
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +25 -1
- data/sig/rigor/type.rbs +95 -1
- data/sig/rigor.rbs +16 -0
- metadata +69 -3
|
@@ -23,7 +23,7 @@ module Rigor
|
|
|
23
23
|
# API stays purely functional.
|
|
24
24
|
#
|
|
25
25
|
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
26
|
-
# rubocop:disable Metrics/ClassLength
|
|
26
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
27
27
|
class RbsLoader
|
|
28
28
|
# Buffer name stamped on the `module` declarations synthesized by {.synthesize_missing_namespaces}.
|
|
29
29
|
# Re-read off the built env by {#synthesized_namespaces} so the analysis layer can surface an `:info`
|
|
@@ -38,6 +38,10 @@ module Rigor
|
|
|
38
38
|
# into the set {MethodDispatcher} resolves to `Dynamic[Top]` (no false `call.undefined-method`).
|
|
39
39
|
SYNTHETIC_STUB_BUFFER = "(rigor: synthesized stub types)"
|
|
40
40
|
|
|
41
|
+
# The buffer name `Cache::RbsEnvironmentMarshalPatch` reconstructs every `RBS::Location` behind on a
|
|
42
|
+
# cache HIT. Never a real path, so it must never be reported as one (issue #696).
|
|
43
|
+
CACHED_LOCATION_BUFFER_NAME = "<cached>"
|
|
44
|
+
|
|
41
45
|
# Cap on how many quarantined `signature_paths:` files {#warn_about_quarantined_signatures} lists by name
|
|
42
46
|
# before collapsing the tail to "… and N more" — a broken generator can emit many, and a wall of parse
|
|
43
47
|
# errors buries the signal.
|
|
@@ -63,7 +67,7 @@ module Rigor
|
|
|
63
67
|
# Vendored gem stubs (`data/vendored_gem_sigs/<gem>/`) are loaded on top of `signature_paths` so the
|
|
64
68
|
# per-gem RBS bundled with Rigor itself is in scope for every analysis run. The gem stubs are
|
|
65
69
|
# intentionally read-only and appended LAST so user-supplied `signature_paths` win on name conflicts.
|
|
66
|
-
def build_env_for(libraries:, signature_paths:, virtual_rbs: [])
|
|
70
|
+
def build_env_for(libraries:, signature_paths:, virtual_rbs: [], deferred_signature_paths: [])
|
|
67
71
|
rbs_loader = RBS::EnvironmentLoader.new
|
|
68
72
|
libraries = libraries_without_shadowed_bigdecimal_math(libraries)
|
|
69
73
|
loaded_libraries = libraries.select do |library|
|
|
@@ -80,7 +84,7 @@ module Rigor
|
|
|
80
84
|
# sigs are Rigor-shipped and trusted, so they stay on the loader's fast batch path.
|
|
81
85
|
add_bundled_signatures(rbs_loader, loaded_libraries.to_set(&:to_s))
|
|
82
86
|
env = RBS::Environment.from_loader(rbs_loader)
|
|
83
|
-
add_project_signatures(env, signature_paths)
|
|
87
|
+
add_project_signatures(env, signature_paths, deferred_signature_paths)
|
|
84
88
|
add_virtual_rbs(env, virtual_rbs)
|
|
85
89
|
synthesize_missing_namespaces(env)
|
|
86
90
|
env, resolved = resolve_quarantining_virtual_collisions(env, virtual_rbs)
|
|
@@ -173,7 +177,7 @@ module Rigor
|
|
|
173
177
|
raise unless env.respond_to?(:unload)
|
|
174
178
|
|
|
175
179
|
culprits = e.decls.filter_map { |decl| decl.location&.buffer&.name }
|
|
176
|
-
|
|
180
|
+
.uniq.select { |name| virtual_names.include?(name) }
|
|
177
181
|
raise if culprits.empty?
|
|
178
182
|
|
|
179
183
|
env = env.unload(culprits)
|
|
@@ -287,14 +291,98 @@ module Rigor
|
|
|
287
291
|
# Buffer names are the file's absolute path (matching {.project_sig_files}) so {.project_entry?} — which
|
|
288
292
|
# attributes a `class_decls` entry to the project by buffer name — still recognises these declarations.
|
|
289
293
|
# Sorted for a deterministic add order (the env feeds the cache, ADR-54).
|
|
290
|
-
def add_project_signatures(env, signature_paths)
|
|
291
|
-
project_sig_files(
|
|
294
|
+
def add_project_signatures(env, signature_paths, deferred_paths = [])
|
|
295
|
+
deferred = project_sig_files(deferred_paths)
|
|
296
|
+
(project_sig_files(signature_paths) - deferred).sort.each do |file|
|
|
292
297
|
parsed = parse_signature_file(file)
|
|
293
298
|
next if parsed.nil? # quarantined (unparseable) or unreadable — skip so the env survives
|
|
294
299
|
|
|
295
300
|
buffer, directives, decls = parsed
|
|
296
301
|
add_parsed_decls(env, buffer, directives, decls)
|
|
297
302
|
end
|
|
303
|
+
add_deferred_signatures(env, deferred)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Issue #610 — the DEFERRED half: signature files a bundled plugin contributes through its manifest,
|
|
307
|
+
# added after every other source so each can be asked a question the eager half cannot be asked —
|
|
308
|
+
# "does something already declare this class, at a different generic arity?".
|
|
309
|
+
#
|
|
310
|
+
# `plugins/rigor-activerecord/sig` declares `class Relation[Elem]`; `rbs collection install` ships a
|
|
311
|
+
# NON-generic `ActiveRecord::Relation`. Two declarations of one class with different arity make
|
|
312
|
+
# `RBS::DefinitionBuilder` raise `GenericParameterMismatchError`, and Rigor keeps the class KNOWN
|
|
313
|
+
# after a failed build, so every call INTO a relation reads `Dynamic[top]` — the plugin and
|
|
314
|
+
# `rigor-project-init`'s own advice to install the collection cancel each other out.
|
|
315
|
+
#
|
|
316
|
+
# The plugin stands down, not the user's source, for the reason `Reflection.constant_type_at`
|
|
317
|
+
# already encodes: the project's own sources are authoritative for the project. Standing down is
|
|
318
|
+
# strictly better than the collision it replaces — the collection's own declaration builds, where
|
|
319
|
+
# today NEITHER does — and it cannot regress a project without a colliding declaration, because
|
|
320
|
+
# nothing triggers.
|
|
321
|
+
#
|
|
322
|
+
# Granularity is the FILE, not the declaration: a bundled plugin's `sig/` file is single-purpose
|
|
323
|
+
# (rigor-activerecord's is `relation.rbs`, declaring exactly `Relation`), so dropping the file drops
|
|
324
|
+
# exactly the conflicting declaration. Splitting a plugin file that mixes a colliding class with
|
|
325
|
+
# innocent ones is the change to make if that ever stops being true.
|
|
326
|
+
def add_deferred_signatures(env, deferred_files)
|
|
327
|
+
deferred_files.sort.each do |file|
|
|
328
|
+
parsed = parse_signature_file(file)
|
|
329
|
+
next if parsed.nil?
|
|
330
|
+
|
|
331
|
+
buffer, directives, decls = parsed
|
|
332
|
+
next if generic_arity_conflict(env, decls)
|
|
333
|
+
|
|
334
|
+
add_parsed_decls(env, buffer, directives, decls)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Issue #610 — which deferred (plugin-contributed) files stood down, as
|
|
339
|
+
# `[absolute_path, class_name, existing_arity, incoming_arity]`, sorted. DERIVED from a built env
|
|
340
|
+
# rather than recorded during the build, for {RbsLoader#quarantined_signatures}' reason: a cache HIT
|
|
341
|
+
# never runs the build, and a condition only the build knew would silently disappear on the second
|
|
342
|
+
# run. Re-asking the same question of the FINAL env gives the same answer — the file's declaration is
|
|
343
|
+
# absent from it, and the source that displaced it is still there with its own arity.
|
|
344
|
+
def deferred_standdowns(env, deferred_paths)
|
|
345
|
+
return [] if env.nil? || deferred_paths.nil? || deferred_paths.empty?
|
|
346
|
+
|
|
347
|
+
project_sig_files(deferred_paths).sort.filter_map do |file|
|
|
348
|
+
parsed = parse_signature_file(file)
|
|
349
|
+
next if parsed.nil?
|
|
350
|
+
|
|
351
|
+
conflict = generic_arity_conflict(env, parsed[2])
|
|
352
|
+
conflict && [file, *conflict]
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# The first `[class_name, existing_arity, incoming_arity]` where `decls` would re-declare a class the
|
|
357
|
+
# env already has with a DIFFERENT number of type parameters, or nil when they agree everywhere.
|
|
358
|
+
# Equal arity is not a conflict: RBS reopens the class, which is the ordinary and supported case, so
|
|
359
|
+
# the stand-down must not fire on it.
|
|
360
|
+
def generic_arity_conflict(env, decls)
|
|
361
|
+
each_declared_class(decls) do |name, arity|
|
|
362
|
+
entry = env.class_decls[::RBS::TypeName.parse(name)]
|
|
363
|
+
next if entry.nil? || !entry.respond_to?(:type_params)
|
|
364
|
+
|
|
365
|
+
existing = entry.type_params&.size
|
|
366
|
+
return [name, existing, arity] if existing && existing != arity
|
|
367
|
+
end
|
|
368
|
+
nil
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Yields `[absolute_class_name, type_param_count]` for every class declared anywhere in `decls`,
|
|
372
|
+
# descending through modules and nested classes. Names inside a `module`/`class` body are parsed
|
|
373
|
+
# RELATIVE, so the enclosing path is accumulated here — the env is keyed by absolute name, and
|
|
374
|
+
# comparing a relative `Relation` against it would silently never match.
|
|
375
|
+
def each_declared_class(decls, prefix = [], &block)
|
|
376
|
+
decls.each do |decl|
|
|
377
|
+
case decl
|
|
378
|
+
when ::RBS::AST::Declarations::Module
|
|
379
|
+
each_declared_class(decl.members, prefix + [decl.name.to_s], &block)
|
|
380
|
+
when ::RBS::AST::Declarations::Class
|
|
381
|
+
inner = prefix + [decl.name.to_s]
|
|
382
|
+
block.call("::#{inner.join('::')}", decl.type_params.size)
|
|
383
|
+
each_declared_class(decl.members, inner, &block)
|
|
384
|
+
end
|
|
385
|
+
end
|
|
298
386
|
end
|
|
299
387
|
|
|
300
388
|
# The quarantine note for a file rejected by {.invalid_encoding?} — worded to be distinct from any
|
|
@@ -758,6 +846,79 @@ module Rigor
|
|
|
758
846
|
end
|
|
759
847
|
end
|
|
760
848
|
|
|
849
|
+
# @param path [String, Pathname] typically an entry from an {RbsLoader} instance's
|
|
850
|
+
# {#signature_paths}.
|
|
851
|
+
# @return [Boolean] whether `path` sits under the bundled gem-overlay root — what
|
|
852
|
+
# `CheckRules::GEM_OVERLAY_OPEN_RECEIVERS`'s gate consults so that a class name's membership in
|
|
853
|
+
# that list alone never grants the open-receiver exemption; the overlay directory that made the
|
|
854
|
+
# entry true must have actually loaded THIS run too (issue #632, tracked further by #660).
|
|
855
|
+
# `GEM_OVERLAY_SIGS_ROOT` is defined inside this `class << self` block, so it is reachable from
|
|
856
|
+
# here directly but NOT as `RbsLoader::GEM_OVERLAY_SIGS_ROOT` from outside — this method is the
|
|
857
|
+
# public seam callers outside this class use instead of reaching for the constant themselves.
|
|
858
|
+
def under_gem_overlay_root?(path)
|
|
859
|
+
path.to_s.start_with?("#{GEM_OVERLAY_SIGS_ROOT}/")
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
# The twin of {.under_gem_overlay_root?}, for the OTHER copy of the same declarations. ADR-72 ships
|
|
863
|
+
# each overlay's surface twice — as `data/gem_overlay/<gem>/` and as the opt-in plugin's own `sig/` —
|
|
864
|
+
# and a project can reach the second WITHOUT the plugin registry, by naming it in `signature_paths:`
|
|
865
|
+
# (issue #672). Two callers ask, for different reasons:
|
|
866
|
+
#
|
|
867
|
+
# - `Environment.gem_overlay_paths` asks per gem, so the overlay stands down instead of loading
|
|
868
|
+
# alongside the twin and collapsing every class they share;
|
|
869
|
+
# - `CheckRules#gem_overlay_loaded?` asks across all of them, because the twin's `sig/` carries no
|
|
870
|
+
# plugin manifest, so `open_receivers:` cannot protect the partial `ActiveSupport::Duration`
|
|
871
|
+
# declaration it nonetheless loads.
|
|
872
|
+
#
|
|
873
|
+
# **Both ask what an entry LOADS, not what it looks like.** Comparing path strings is wrong in both
|
|
874
|
+
# directions, and the false-positive direction is the expensive one: an entry naming the twin's
|
|
875
|
+
# `.rbs` FILE, or a subdirectory of the twin that does not exist, matches as a string while the
|
|
876
|
+
# loader reads nothing from it ({.project_sig_files} takes directories only — the same fact
|
|
877
|
+
# {Rigor::SignaturePathAudit} reports as `:not_directory` / `:missing`). Standing an overlay down for
|
|
878
|
+
# those leaves the project with NEITHER copy, so ordinary `3.minutes` draws `call.undefined-method`
|
|
879
|
+
# on correct code. The quiet direction is the mirror: a symlink to the twin, or a case-variant
|
|
880
|
+
# spelling of it where the filesystem folds case, loads the twin while matching no prefix. Asking
|
|
881
|
+
# {.project_sig_files} for the twin's files and canonicalising both sides answers both.
|
|
882
|
+
#
|
|
883
|
+
# Lives here rather than on {Rigor::Environment} because it is an internal seam, not ADR-2 public
|
|
884
|
+
# API: `spec/rigor/public_api_drift_spec.rb` pins `Environment`'s singleton surface, and a predicate
|
|
885
|
+
# two call sites share is not a promise to plugin authors. Here it sits beside the overlay-side
|
|
886
|
+
# answer to the very same question, on the class that already owns the overlay's layout
|
|
887
|
+
# ({GEM_OVERLAY_SIGS_ROOT}, {.gem_overlay_sig_paths}).
|
|
888
|
+
#
|
|
889
|
+
# @param signature_paths [Array<String, Pathname>] typically an {RbsLoader} instance's
|
|
890
|
+
# {#signature_paths}. Takes the whole list rather than one entry so the twin's file set resolves
|
|
891
|
+
# once per question — `CheckRules` asks it per `ActiveSupport::Duration` receiver.
|
|
892
|
+
def gem_overlay_twin_signatures_loaded?(signature_paths)
|
|
893
|
+
# `GEM_OVERLAY_PLUGIN_IDS` is ADR-72 eligibility policy and stays owned by `Environment`; it is
|
|
894
|
+
# read here only to enumerate which bundled plugins HAVE an overlay twin.
|
|
895
|
+
GEM_OVERLAY_PLUGIN_IDS.each_value.any? do |plugin_id|
|
|
896
|
+
bundled_overlay_twin_signatures_loaded?(plugin_id, signature_paths)
|
|
897
|
+
end
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
# The per-plugin half of {.gem_overlay_twin_signatures_loaded?}. `Environment` owns the gem → plugin
|
|
901
|
+
# id mapping and passes the id; this resolves the id to the engine's own bundled `sig/` and answers
|
|
902
|
+
# the filesystem question.
|
|
903
|
+
#
|
|
904
|
+
# @param plugin_id [String] a manifest id, e.g. `"activesupport-core-ext"`.
|
|
905
|
+
def bundled_overlay_twin_signatures_loaded?(plugin_id, signature_paths)
|
|
906
|
+
return false if signature_paths.nil? || signature_paths.empty?
|
|
907
|
+
|
|
908
|
+
# In-method for the reason `Plugin::FirstParty` documents at its own `require`: the plugin
|
|
909
|
+
# subsystem's load graph reaches back into the environment, and only the overlay path calls this —
|
|
910
|
+
# a run with no eligible overlay gem never pays them.
|
|
911
|
+
require_relative "../plugin/first_party"
|
|
912
|
+
require_relative "../plugin/loader"
|
|
913
|
+
twin = Plugin::Loader.bundled_plugin_sig_path("#{Plugin::FirstParty::GEM_PREFIX}#{plugin_id}")
|
|
914
|
+
return false unless twin
|
|
915
|
+
|
|
916
|
+
twin_files = project_sig_files([twin]).filter_map { |file| canonical_real_path(file) }
|
|
917
|
+
return false if twin_files.empty?
|
|
918
|
+
|
|
919
|
+
signature_paths.any? { |entry| signature_entry_loads?(entry, twin_files) }
|
|
920
|
+
end
|
|
921
|
+
|
|
761
922
|
def vendored_gem_sig_paths
|
|
762
923
|
return [] unless File.directory?(VENDORED_GEM_SIGS_ROOT)
|
|
763
924
|
|
|
@@ -778,6 +939,34 @@ module Rigor
|
|
|
778
939
|
File.file?(File.join(VENDORED_GEM_SIGS_ROOT, child))
|
|
779
940
|
end
|
|
780
941
|
end
|
|
942
|
+
|
|
943
|
+
# Everything below is internal to {.bundled_overlay_twin_signatures_loaded?}. `private` (not
|
|
944
|
+
# `private_class_method`) because inside `class << self` these are instance methods of the
|
|
945
|
+
# singleton class; `private_class_method` looks for them one level further out and raises NameError
|
|
946
|
+
# at load. Placed last so the modifier cannot capture a method that is meant to be reachable.
|
|
947
|
+
private
|
|
948
|
+
|
|
949
|
+
# Whether one `signature_paths:` entry is a directory the loader would read one of `twin_files`
|
|
950
|
+
# from. Both sides are canonicalised first, which is what makes a symlinked or case-variant
|
|
951
|
+
# spelling of the twin answer the same as the twin itself.
|
|
952
|
+
def signature_entry_loads?(entry, twin_files)
|
|
953
|
+
dir = canonical_real_path(entry)
|
|
954
|
+
return false unless dir && File.directory?(dir)
|
|
955
|
+
|
|
956
|
+
prefix = "#{dir}#{File::SEPARATOR}"
|
|
957
|
+
twin_files.any? { |file| file.start_with?(prefix) }
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# `File.realpath` rather than `File.expand_path`: it resolves symlinks and, on a case-insensitive
|
|
961
|
+
# filesystem, the on-disk spelling — the two ways a path can reach the twin's files without looking
|
|
962
|
+
# like it. It raises for a path that does not exist, which is the answer we want (an entry that
|
|
963
|
+
# resolves to nothing loads nothing), so the rescue returns nil rather than falling back to a
|
|
964
|
+
# lexical expansion that would resurrect the string match.
|
|
965
|
+
def canonical_real_path(path)
|
|
966
|
+
File.realpath(path.to_s)
|
|
967
|
+
rescue SystemCallError
|
|
968
|
+
nil
|
|
969
|
+
end
|
|
781
970
|
end
|
|
782
971
|
|
|
783
972
|
attr_reader :libraries, :signature_paths, :cache_store, :virtual_rbs
|
|
@@ -797,15 +986,22 @@ module Rigor
|
|
|
797
986
|
# synthesised from project source by a plugin's `Manifest#source_rbs_synthesizer`. Merged into the
|
|
798
987
|
# env after `signature_paths:` and the vendored stubs. Pass `[]` (the default) when no
|
|
799
988
|
# synthesizer-emitting plugin is loaded.
|
|
800
|
-
def initialize(libraries: [], signature_paths: [], cache_store: nil, virtual_rbs: []
|
|
989
|
+
def initialize(libraries: [], signature_paths: [], cache_store: nil, virtual_rbs: [],
|
|
990
|
+
deferred_signature_paths: [])
|
|
801
991
|
@libraries = libraries.map(&:to_s).freeze
|
|
802
992
|
@signature_paths = signature_paths.map { |p| Pathname(p) }.freeze
|
|
993
|
+
# Issue #610 — the subset of `signature_paths:` a bundled PLUGIN contributed. Loaded last and
|
|
994
|
+
# allowed to stand down against a colliding generic arity ({.add_deferred_signatures}). Every entry
|
|
995
|
+
# is also in `@signature_paths`, so the env cache descriptor already digests these files.
|
|
996
|
+
@deferred_signature_paths = deferred_signature_paths.map { |p| Pathname(p) }.freeze
|
|
803
997
|
@cache_store = cache_store
|
|
804
998
|
@virtual_rbs = virtual_rbs.map { |name, content| [name.to_s.dup.freeze, content.to_s.dup.freeze].freeze }.freeze
|
|
805
999
|
# Per-loader memoization bucket. Held as a single mutable Hash so the loader instance itself can be
|
|
806
1000
|
# `.freeze`d (per ADR-15 reflection-facade contract) without losing the lazy-memo behaviour. Slot
|
|
807
1001
|
# names currently consulted: `:env`, `:env_loaded`, `:env_build_warned`, `:definition_build_warned`,
|
|
808
|
-
# `:
|
|
1002
|
+
# `:definition_build_details`, `:definition_build_reported`, `:definition_build_failures`,
|
|
1003
|
+
# `:internal_demand`, `:builder`, `:reflection`, `:instance_definitions_table`,
|
|
1004
|
+
# `:singleton_definitions_table`.
|
|
809
1005
|
# Constructed via `Hash.new` (NOT a `{ ... }` literal) so Rigor's `HashShape` narrowing doesn't
|
|
810
1006
|
# infer a fixed key set from the initial state and fold post-initial slot reads (e.g.
|
|
811
1007
|
# `@state[:env_loaded]`) to a constant `nil`.
|
|
@@ -838,6 +1034,54 @@ module Rigor
|
|
|
838
1034
|
@state[:quarantined] ||= self.class.quarantined_project_signatures(@signature_paths).freeze
|
|
839
1035
|
end
|
|
840
1036
|
|
|
1037
|
+
# Issue #610 — plugin-contributed signature files that stood down against a colliding generic arity,
|
|
1038
|
+
# as `[absolute_path, class_name, existing_arity, incoming_arity]`. Empty whenever no plugin sig
|
|
1039
|
+
# collides, which is every project that has not run `rbs collection install` for a gem a bundled
|
|
1040
|
+
# plugin also declares.
|
|
1041
|
+
def signature_standdowns
|
|
1042
|
+
@state[:signature_standdowns] ||= self.class.deferred_standdowns(env, @deferred_signature_paths).freeze
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
# Issue #735 — the class / module names whose PRIMARY declaration lives under the project's own
|
|
1046
|
+
# `signature_paths:`, top-level prefix stripped ("::Admin::Widget" reads back as "Admin::Widget").
|
|
1047
|
+
#
|
|
1048
|
+
# The distinction it exists to draw: a BUNDLED declaration (core, stdlib, a gem's RBS) is
|
|
1049
|
+
# authoritative about its class, so a project `def` on that class is a monkey-patch and ADR-17 has
|
|
1050
|
+
# the analyzer report it rather than adopt it. A declaration the project wrote itself — by hand or
|
|
1051
|
+
# through `rigor sig-gen --write` — is a sidecar of the same source tree, and a `def` in another of
|
|
1052
|
+
# the project's own files is not a monkey-patch, just the second file of an ordinary class. Reading
|
|
1053
|
+
# a partial sidecar as authoritative made `sig-gen --write` on redmine report 5.9x more
|
|
1054
|
+
# `call.undefined-method` than the same project with no `sig/` at all.
|
|
1055
|
+
#
|
|
1056
|
+
# Memoised per loader: one pass over `class_decls` (~1-2k entries), and every consumer is a check
|
|
1057
|
+
# rule on the analysis hot path. Attribution is by buffer NAME, which survives the ADR-54 environment
|
|
1058
|
+
# cache since #725 — an environment blob written before that carries the `<cached>` sentinel instead,
|
|
1059
|
+
# which lands every class outside this set and leaves the pre-#735 behaviour, never a new silence.
|
|
1060
|
+
def project_declared_classes
|
|
1061
|
+
@state[:project_declared_classes] ||= build_project_declared_classes
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
def build_project_declared_classes
|
|
1065
|
+
environment = env
|
|
1066
|
+
return Set.new.freeze if environment.nil?
|
|
1067
|
+
|
|
1068
|
+
project_files = self.class.project_sig_files(@signature_paths)
|
|
1069
|
+
return Set.new.freeze if project_files.empty?
|
|
1070
|
+
|
|
1071
|
+
names = environment.class_decls.each_with_object(Set.new) do |(rbs_name, entry), acc|
|
|
1072
|
+
acc << rbs_name.to_s.delete_prefix("::") if self.class.project_entry?(entry, project_files)
|
|
1073
|
+
end
|
|
1074
|
+
names.freeze
|
|
1075
|
+
rescue ::RBS::BaseError
|
|
1076
|
+
Set.new.freeze
|
|
1077
|
+
end
|
|
1078
|
+
private :build_project_declared_classes
|
|
1079
|
+
|
|
1080
|
+
# True when `class_name`'s RBS declaration is one the project wrote (see {#project_declared_classes}).
|
|
1081
|
+
def project_declared_class?(class_name)
|
|
1082
|
+
project_declared_classes.include?(class_name.to_s.delete_prefix("::"))
|
|
1083
|
+
end
|
|
1084
|
+
|
|
841
1085
|
# The total RBS-environment build failure captured this run, or nil when the env built. Unlike
|
|
842
1086
|
# {#quarantined_signatures} — which the env survives, one file lighter, and which is re-derived by
|
|
843
1087
|
# re-parsing so a cache HIT reports it too — a total failure (typically `RBS::DuplicatedDeclarationError`:
|
|
@@ -853,6 +1097,30 @@ module Rigor
|
|
|
853
1097
|
@state[:env_build_failure]
|
|
854
1098
|
end
|
|
855
1099
|
|
|
1100
|
+
# Issue #696 — the PER-CLASS sibling of {#env_build_failure}, one tier quieter in consequence: an
|
|
1101
|
+
# `RBS::DefinitionBuilder` failure ({#build_instance_definition} / {#build_singleton_definition}'s
|
|
1102
|
+
# rescue) leaves the class KNOWN but with no method surface, so every call on it — real methods and
|
|
1103
|
+
# typos alike — reads `Dynamic[top]` and stops being checkable. `class_known?` consults
|
|
1104
|
+
# {#known_class_names_set}, never a definition build, so nothing downstream can tell the difference.
|
|
1105
|
+
#
|
|
1106
|
+
# NOT forced the way {#env_build_failure} forces `env`, and it MUST NOT be: definition builds are lazy
|
|
1107
|
+
# (ADR-54 WD1 — per class, on first demand), so this answers "which classes has THIS loader failed to
|
|
1108
|
+
# build so far". A reader that forces would have to build every known class, which is a different and
|
|
1109
|
+
# far more expensive question than the run asked. The consequence for callers is a timing contract: a
|
|
1110
|
+
# snapshot taken before the per-file loop reads empty. `Runner::PoolCoordinator` reads it after.
|
|
1111
|
+
#
|
|
1112
|
+
# Recorded, not derived. The two conditions this sits beside are re-derivable from the built env
|
|
1113
|
+
# ({#quarantined_signatures} re-parses; {#virtual_rbs_collision_quarantined} inspects buffers), but a
|
|
1114
|
+
# definition-build failure leaves no trace in the env at all — the env is fine; it is the BUILD over it
|
|
1115
|
+
# that raised — so the rescue is the only place that ever knows.
|
|
1116
|
+
#
|
|
1117
|
+
# @return [Array<Array(String, String, String, Array<String>)>] `[class_name, error_class_name,
|
|
1118
|
+
# first_error_line, conflicting_buffer_names]`, one per class, in first-failure order. Empty for a
|
|
1119
|
+
# healthy sig set, which is the common case.
|
|
1120
|
+
def definition_build_failures
|
|
1121
|
+
(@state[:definition_build_failures] || []).dup.freeze
|
|
1122
|
+
end
|
|
1123
|
+
|
|
856
1124
|
# Virtual (inline-synthesized) contributions dropped by the collision quarantine
|
|
857
1125
|
# ({.resolve_quarantining_virtual_collisions}): buffer names absent from the built env even though the
|
|
858
1126
|
# entry's content is non-empty and parses (a parse failure is the synthesizer's own WD6 skip, reported
|
|
@@ -951,8 +1219,25 @@ module Rigor
|
|
|
951
1219
|
# silently the way the v0.0.9 cache `Cache::Descriptor` regression did.
|
|
952
1220
|
end
|
|
953
1221
|
|
|
1222
|
+
# Yields every type-alias declaration loaded into the environment (`type foo = ...`).
|
|
1223
|
+
# Yields `(RBS::TypeName, RBS::Environment::TypeAliasEntry)` pairs.
|
|
1224
|
+
def each_type_alias_decl
|
|
1225
|
+
return enum_for(:each_type_alias_decl) unless block_given?
|
|
1226
|
+
return unless env
|
|
1227
|
+
|
|
1228
|
+
env.type_alias_decls.each do |type_name, decl_entry|
|
|
1229
|
+
yield [type_name, decl_entry]
|
|
1230
|
+
end
|
|
1231
|
+
end
|
|
1232
|
+
|
|
954
1233
|
# ADR-20 slice 2e — iterates over every `%a{...}` annotation attached to a class- or module-level
|
|
955
1234
|
# declaration in the loaded RBS environment, yielding `(annotation_string, source_location)` pairs.
|
|
1235
|
+
#
|
|
1236
|
+
# Declarations come from {.entry_declarations}, NOT from a direct `entry.each_decl`: that accessor is
|
|
1237
|
+
# RBS 4.x-only, so the direct call raised `NoMethodError` on every RBS 3.x host — inside a rescue that
|
|
1238
|
+
# deliberately does not swallow `NoMethodError`. It stayed latent because nothing the `rbs-compat` job
|
|
1239
|
+
# runs reached an annotated declaration until #672 added a spec under `spec/rigor/environment` that
|
|
1240
|
+
# drives a whole `Runner`.
|
|
956
1241
|
# Used by {Rigor::Inference::HktRegistry.scan_rbs_loader} to find `rigor:v1:hkt_register` /
|
|
957
1242
|
# `rigor:v1:hkt_define` directives in user-authored overlays and merge them into the per-`Environment`
|
|
958
1243
|
# HKT registry. Yields nothing when the env failed to build (fail-soft, same shape as
|
|
@@ -962,7 +1247,7 @@ module Rigor
|
|
|
962
1247
|
return if env.nil?
|
|
963
1248
|
|
|
964
1249
|
env.class_decls.each_value do |entry|
|
|
965
|
-
entry.
|
|
1250
|
+
self.class.entry_declarations(entry).each do |decl|
|
|
966
1251
|
next unless decl.respond_to?(:annotations)
|
|
967
1252
|
|
|
968
1253
|
decl.annotations.each { |a| yield a.string, a.location }
|
|
@@ -983,7 +1268,7 @@ module Rigor
|
|
|
983
1268
|
return if env.nil?
|
|
984
1269
|
|
|
985
1270
|
env.class_decls.each do |rbs_name, entry|
|
|
986
|
-
entry.
|
|
1271
|
+
self.class.entry_declarations(entry).each do |decl|
|
|
987
1272
|
next unless decl.respond_to?(:annotations)
|
|
988
1273
|
|
|
989
1274
|
decl.annotations.each { |a| yield rbs_name.to_s, a.string, a.location }
|
|
@@ -1029,11 +1314,22 @@ module Rigor
|
|
|
1029
1314
|
# keeps the per-process short-circuit. ADR-54 WD1 retired the definitions disk blob: given a cached
|
|
1030
1315
|
# env, `Marshal.load`-ing every definition was measurably slower (and allocation-heavier) than
|
|
1031
1316
|
# rebuilding the ones a run actually touches.
|
|
1317
|
+
#
|
|
1318
|
+
# Issue #696 — the report fires HERE, on the result, rather than in `build_*`'s rescue, and it fires on
|
|
1319
|
+
# a memo hit too. `#prewarm`'s cached producers walk every known class through this method, so by the
|
|
1320
|
+
# time a run demands `String` the nil is already memoised and the rescue will never run again; a report
|
|
1321
|
+
# wired to the rescue would say nothing. The guard keeps the cost on the hot path at one `@state` read,
|
|
1322
|
+
# and only on the nil branch — `nil` here is overwhelmingly an unknown class, not a failed build.
|
|
1032
1323
|
def instance_definition(class_name)
|
|
1033
1324
|
key = class_name.to_s
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1325
|
+
definition =
|
|
1326
|
+
if @instance_definition_cache.key?(key)
|
|
1327
|
+
@instance_definition_cache[key]
|
|
1328
|
+
else
|
|
1329
|
+
@instance_definition_cache[key] = build_instance_definition(class_name)
|
|
1330
|
+
end
|
|
1331
|
+
report_definition_build_failure(class_name) if definition.nil? && @state[:definition_build_details]
|
|
1332
|
+
definition
|
|
1037
1333
|
end
|
|
1038
1334
|
|
|
1039
1335
|
# @return [RBS::Definition::Method, nil]
|
|
@@ -1091,7 +1387,14 @@ module Rigor
|
|
|
1091
1387
|
name = name.absolute! unless name.absolute?
|
|
1092
1388
|
return nil unless env.type_alias_decls.key?(name)
|
|
1093
1389
|
|
|
1094
|
-
|
|
1390
|
+
# Memoized per (name, args): the env is immutable for the loader's lifetime, so the expansion is a
|
|
1391
|
+
# pure function of the pair, and #529's translator wiring re-expands the same handful of aliases
|
|
1392
|
+
# (`Prism::node`, `int`, `string`, …) at every call site — the memo recoups most of that wall cost.
|
|
1393
|
+
memo = (@state[:type_alias_expansions] ||= {})
|
|
1394
|
+
key = [name, rbs_alias.args]
|
|
1395
|
+
return memo[key] if memo.key?(key)
|
|
1396
|
+
|
|
1397
|
+
memo[key] = builder.expand_alias2(name, rbs_alias.args)
|
|
1095
1398
|
rescue ::RBS::BaseError, StandardError
|
|
1096
1399
|
nil
|
|
1097
1400
|
end
|
|
@@ -1103,11 +1406,18 @@ module Rigor
|
|
|
1103
1406
|
#
|
|
1104
1407
|
# Built on demand from the env with a per-process memo; the same on-demand discipline as
|
|
1105
1408
|
# {#instance_definition} (ADR-54 WD1).
|
|
1409
|
+
#
|
|
1410
|
+
# The singleton twin of {#instance_definition}, reporting on the same terms (issue #696).
|
|
1106
1411
|
def singleton_definition(class_name)
|
|
1107
1412
|
key = class_name.to_s
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1413
|
+
definition =
|
|
1414
|
+
if @singleton_definition_cache.key?(key)
|
|
1415
|
+
@singleton_definition_cache[key]
|
|
1416
|
+
else
|
|
1417
|
+
@singleton_definition_cache[key] = build_singleton_definition(class_name)
|
|
1418
|
+
end
|
|
1419
|
+
report_definition_build_failure(class_name) if definition.nil? && @state[:definition_build_details]
|
|
1420
|
+
definition
|
|
1111
1421
|
end
|
|
1112
1422
|
|
|
1113
1423
|
# @return [RBS::Definition::Method, nil] the class method on `class_name`. For example,
|
|
@@ -1150,6 +1460,47 @@ module Rigor
|
|
|
1150
1460
|
@hierarchy.class_ordering(lhs, rhs)
|
|
1151
1461
|
end
|
|
1152
1462
|
|
|
1463
|
+
# The ancestor chain of ONE class, as {RbsHierarchy} asks it to order two classes.
|
|
1464
|
+
#
|
|
1465
|
+
# Issue #696 review (second pass) — this used to live in the hierarchy, which branched on
|
|
1466
|
+
# `cache_store`: with a store it built the whole `RbsClassAncestorTable` and read one key, without a
|
|
1467
|
+
# store it demanded that one class. Three configurations, three different answers to "which classes
|
|
1468
|
+
# failed to build" for one project — 504 classes on a cold store, 0 on a warm one, 2 with no store —
|
|
1469
|
+
# and on the DEFAULT `--workers=0` path, where nothing pre-warms the store first, the cold answer was
|
|
1470
|
+
# the one written into the run-result cache and replayed on every warm run after it.
|
|
1471
|
+
#
|
|
1472
|
+
# The fix is {#during_internal_demand} on BOTH sides, not the removal of the branch. Collapsing to a
|
|
1473
|
+
# per-class demand everywhere was tried first and measured 50x slower on the warm path a cached run
|
|
1474
|
+
# actually takes (200 orderings: 0.0035s reading the table, 0.18s building 400 definitions), which is
|
|
1475
|
+
# the wrong trade for a determinism the marking already buys. What has to be identical across cache
|
|
1476
|
+
# states is the diagnostic, and with both sides marked neither contributes to it: the reported set is
|
|
1477
|
+
# the classes whose METHOD SURFACE the analysis demanded, in every configuration. The memo-hit report
|
|
1478
|
+
# in {#instance_definition} is what makes that hold — a class this path built and memoised as nil is
|
|
1479
|
+
# still reported when a real demand for it arrives later.
|
|
1480
|
+
#
|
|
1481
|
+
# The table side goes through {#ancestor_names_table}, the loader's own marked accessor, rather than
|
|
1482
|
+
# `Cache::RbsClassAncestorTable.fetch` directly. That direct fetch was the bypass: it walked every
|
|
1483
|
+
# known class through the public {#instance_definition} with nothing marking it.
|
|
1484
|
+
#
|
|
1485
|
+
# Answers are identical on both sides — the table's producer computes exactly this, keyed the same
|
|
1486
|
+
# way, and both yield `[]` for an unknown or unbuildable class. Pinned by spec across all three cache
|
|
1487
|
+
# states.
|
|
1488
|
+
#
|
|
1489
|
+
# @return [Array<String>] `::`-stripped ancestor names, or `[]` for an unknown or unbuildable class.
|
|
1490
|
+
def ancestor_names_for(class_name)
|
|
1491
|
+
key = class_name.to_s.delete_prefix("::")
|
|
1492
|
+
during_internal_demand do
|
|
1493
|
+
next ancestor_names_table.fetch(key, [].freeze) if cache_store
|
|
1494
|
+
|
|
1495
|
+
definition = instance_definition(key)
|
|
1496
|
+
next [].freeze if definition.nil?
|
|
1497
|
+
|
|
1498
|
+
definition.ancestors.ancestors.map { |ancestor| ancestor.name.to_s.delete_prefix("::") }.uniq.freeze
|
|
1499
|
+
end
|
|
1500
|
+
rescue ::RBS::BaseError, StandardError
|
|
1501
|
+
[].freeze
|
|
1502
|
+
end
|
|
1503
|
+
|
|
1153
1504
|
# @return [Array<String>] every RBS-declared constant name (top-level prefixed, e.g., `"::Math::PI"`)
|
|
1154
1505
|
# currently loaded into the environment. Used by the cache producer that materialises the
|
|
1155
1506
|
# constant-type table; ordinary callers should keep using {#constant_type} for point lookups.
|
|
@@ -1210,16 +1561,23 @@ module Rigor
|
|
|
1210
1561
|
# No-op when `cache_store` is nil — without a Store the worker has no choice but to build env via the
|
|
1211
1562
|
# loader, so the caller MUST ensure pool mode runs with caching enabled. Returns `self` so the call
|
|
1212
1563
|
# chains cleanly from the `Runner` pre-spawn hook.
|
|
1564
|
+
#
|
|
1565
|
+
# Issue #696 — the whole body is a {#during_internal_demand}, not only the producers that happen to walk
|
|
1566
|
+
# definitions today. On a COLD store `RbsClassTypeParamNames.compute` and `RbsClassAncestorTable.compute`
|
|
1567
|
+
# ask {#instance_definition} for every known class; marking each producer covers that, and marking
|
|
1568
|
+
# `#prewarm` itself covers whichever producer grows the same appetite next.
|
|
1213
1569
|
def prewarm
|
|
1214
1570
|
return self if cache_store.nil?
|
|
1215
1571
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1572
|
+
during_internal_demand do
|
|
1573
|
+
env
|
|
1574
|
+
known_class_names_set
|
|
1575
|
+
constant_type_table
|
|
1576
|
+
type_param_names_table
|
|
1577
|
+
ancestor_names_table
|
|
1578
|
+
instance_definitions_table
|
|
1579
|
+
singleton_definitions_table
|
|
1580
|
+
end
|
|
1223
1581
|
self
|
|
1224
1582
|
end
|
|
1225
1583
|
|
|
@@ -1318,21 +1676,21 @@ module Rigor
|
|
|
1318
1676
|
end
|
|
1319
1677
|
|
|
1320
1678
|
def constant_type_table
|
|
1321
|
-
@constant_type_table ||=
|
|
1679
|
+
@constant_type_table ||= during_internal_demand do
|
|
1322
1680
|
require_relative "../cache/rbs_constant_table"
|
|
1323
1681
|
fetch_or_compute_producer(Cache::RbsConstantTable)
|
|
1324
1682
|
end
|
|
1325
1683
|
end
|
|
1326
1684
|
|
|
1327
1685
|
def known_class_names_set
|
|
1328
|
-
@known_class_names_set ||=
|
|
1686
|
+
@known_class_names_set ||= during_internal_demand do
|
|
1329
1687
|
require_relative "../cache/rbs_known_class_names"
|
|
1330
1688
|
fetch_or_compute_producer(Cache::RbsKnownClassNames)
|
|
1331
1689
|
end
|
|
1332
1690
|
end
|
|
1333
1691
|
|
|
1334
1692
|
def type_param_names_table
|
|
1335
|
-
@type_param_names_table ||=
|
|
1693
|
+
@type_param_names_table ||= during_internal_demand do
|
|
1336
1694
|
require_relative "../cache/rbs_class_type_param_names"
|
|
1337
1695
|
fetch_or_compute_producer(Cache::RbsClassTypeParamNames)
|
|
1338
1696
|
end
|
|
@@ -1352,7 +1710,7 @@ module Rigor
|
|
|
1352
1710
|
# the existing `RbsClassAncestorTable` producer when `cache_store` is set; falls back to the
|
|
1353
1711
|
# producer's `compute` otherwise. Used by {#reflection}.
|
|
1354
1712
|
def ancestor_names_table
|
|
1355
|
-
@ancestor_names_table ||=
|
|
1713
|
+
@ancestor_names_table ||= during_internal_demand do
|
|
1356
1714
|
require_relative "../cache/rbs_class_ancestor_table"
|
|
1357
1715
|
fetch_or_compute_producer(Cache::RbsClassAncestorTable)
|
|
1358
1716
|
end
|
|
@@ -1509,15 +1867,20 @@ module Rigor
|
|
|
1509
1867
|
# all definitions from a cached env is faster), so the eager-table cost is now a compute, not a load.
|
|
1510
1868
|
# Keys stay in `RBS::TypeName#to_s` form (top-level prefixed `"::Hash"`) — the shape
|
|
1511
1869
|
# {Environment::Reflection} documents.
|
|
1870
|
+
#
|
|
1871
|
+
# {#during_internal_demand} (issue #696) — these two walk EVERY known class, so a failure they hit is a
|
|
1872
|
+
# failure of the sig set, not of anything the run asked about, and it must not reach
|
|
1873
|
+
# {#definition_build_failures}. The stderr banner is deliberately left armed here, byte-for-byte as
|
|
1874
|
+
# before.
|
|
1512
1875
|
def instance_definitions_table
|
|
1513
|
-
@state[:instance_definitions_table] ||=
|
|
1514
|
-
build_instance_definition(name)
|
|
1876
|
+
@state[:instance_definitions_table] ||= during_internal_demand do
|
|
1877
|
+
build_definitions_table { |name| build_instance_definition(name) }
|
|
1515
1878
|
end
|
|
1516
1879
|
end
|
|
1517
1880
|
|
|
1518
1881
|
def singleton_definitions_table
|
|
1519
|
-
@state[:singleton_definitions_table] ||=
|
|
1520
|
-
build_singleton_definition(name)
|
|
1882
|
+
@state[:singleton_definitions_table] ||= during_internal_demand do
|
|
1883
|
+
build_definitions_table { |name| build_singleton_definition(name) }
|
|
1521
1884
|
end
|
|
1522
1885
|
end
|
|
1523
1886
|
|
|
@@ -1538,7 +1901,8 @@ module Rigor
|
|
|
1538
1901
|
self.class.build_env_for(
|
|
1539
1902
|
libraries: @libraries,
|
|
1540
1903
|
signature_paths: @signature_paths,
|
|
1541
|
-
virtual_rbs: @virtual_rbs
|
|
1904
|
+
virtual_rbs: @virtual_rbs,
|
|
1905
|
+
deferred_signature_paths: @deferred_signature_paths
|
|
1542
1906
|
)
|
|
1543
1907
|
end
|
|
1544
1908
|
|
|
@@ -1552,6 +1916,7 @@ module Rigor
|
|
|
1552
1916
|
|
|
1553
1917
|
builder.build_instance(rbs_name)
|
|
1554
1918
|
rescue ::RBS::BaseError => e
|
|
1919
|
+
store_definition_build_detail(class_name, e)
|
|
1555
1920
|
warn_about_definition_build_failure(class_name, e)
|
|
1556
1921
|
nil
|
|
1557
1922
|
end
|
|
@@ -1566,10 +1931,94 @@ module Rigor
|
|
|
1566
1931
|
|
|
1567
1932
|
builder.build_singleton(rbs_name)
|
|
1568
1933
|
rescue ::RBS::BaseError => e
|
|
1934
|
+
store_definition_build_detail(class_name, e)
|
|
1569
1935
|
warn_about_definition_build_failure(class_name, e)
|
|
1570
1936
|
nil
|
|
1571
1937
|
end
|
|
1572
1938
|
|
|
1939
|
+
# Issue #696 — the DETAIL half of the same rescue: what went wrong for this class, remembered so
|
|
1940
|
+
# {#definition_build_failures} can report it. Split from the REPORTING decision on purpose, and the
|
|
1941
|
+
# split is the whole fix.
|
|
1942
|
+
#
|
|
1943
|
+
# Recording in the rescue is wrong in both directions at once. Too loud: `#prewarm` and the cached
|
|
1944
|
+
# table producers walk EVERY known class, so on a cold store they enter this rescue for every class a
|
|
1945
|
+
# collision took down — 1,336 of them for one `class Object` duplicate — and the diagnostic named all
|
|
1946
|
+
# of them under `--workers=N` with a cold cache while naming 3 under every other configuration. Too
|
|
1947
|
+
# quiet: {#instance_definition} memoises the nil, so once that walk has run, the FIRST real demand
|
|
1948
|
+
# never re-enters the rescue and would record nothing at all.
|
|
1949
|
+
#
|
|
1950
|
+
# So the rescue only remembers, keyed by the `::`-stripped name (the eager walk spells it `::Acme`,
|
|
1951
|
+
# a dispatch site spells it `Acme`, and they are one class — the {#synthesized_namespaces}
|
|
1952
|
+
# convention), and {#report_definition_build_failure} decides. First writer wins: the instance and
|
|
1953
|
+
# singleton sides fail for the same underlying collision, and the first is the one a caller hit.
|
|
1954
|
+
def store_definition_build_detail(class_name, error)
|
|
1955
|
+
key = class_name.to_s.delete_prefix("::")
|
|
1956
|
+
details = (@state[:definition_build_details] ||= {})
|
|
1957
|
+
return if details.key?(key)
|
|
1958
|
+
|
|
1959
|
+
details[key] = [key, error.class.name.to_s, definition_build_member(error),
|
|
1960
|
+
definition_build_conflict_buffers(error)].freeze
|
|
1961
|
+
end
|
|
1962
|
+
|
|
1963
|
+
# Issue #696 — the REPORTING half: promote a remembered detail into {#definition_build_failures}
|
|
1964
|
+
# because something the run was actually doing asked for this class's definition and got nothing.
|
|
1965
|
+
#
|
|
1966
|
+
# Called from {#instance_definition} / {#singleton_definition} — the demand entries — rather than from
|
|
1967
|
+
# the rescue, so it fires on a MEMO HIT too. That is what survives a pre-warm: the walk builds the
|
|
1968
|
+
# definition, fails, memoises nil, and never enters the rescue again, but the next real demand still
|
|
1969
|
+
# reads nil here and still reports. Reporting from the rescue could only ever see the first build.
|
|
1970
|
+
#
|
|
1971
|
+
# Silent inside a whole-universe walk ({#during_internal_demand}): a producer that touches every known
|
|
1972
|
+
# class is asking a question the RUN did not ask, and letting it contribute makes the reported class
|
|
1973
|
+
# list depend on whether a cache was cold — the same project saying different things under
|
|
1974
|
+
# `--workers=N` than under `--workers=0`, which is the defect this diagnostic exists to end.
|
|
1975
|
+
def report_definition_build_failure(class_name)
|
|
1976
|
+
return if @state[:internal_demand]
|
|
1977
|
+
|
|
1978
|
+
details = @state[:definition_build_details]
|
|
1979
|
+
return if details.nil?
|
|
1980
|
+
|
|
1981
|
+
key = class_name.to_s.delete_prefix("::")
|
|
1982
|
+
detail = details[key]
|
|
1983
|
+
return if detail.nil?
|
|
1984
|
+
|
|
1985
|
+
reported = (@state[:definition_build_reported] ||= {})
|
|
1986
|
+
return if reported[key]
|
|
1987
|
+
|
|
1988
|
+
reported[key] = true
|
|
1989
|
+
(@state[:definition_build_failures] ||= []) << detail
|
|
1990
|
+
end
|
|
1991
|
+
|
|
1992
|
+
# Marks a definition demand that is RIGOR'S OWN, not the analysed program's, so
|
|
1993
|
+
# {#report_definition_build_failure} stays silent for the duration.
|
|
1994
|
+
#
|
|
1995
|
+
# The diagnostic reports classes whose METHOD SURFACE the analysis asked to resolve — that is what its
|
|
1996
|
+
# message promises, and it is the only demand set that does not vary with how the run was invoked. Two
|
|
1997
|
+
# kinds of demand are Rigor's own and must be excluded:
|
|
1998
|
+
#
|
|
1999
|
+
# - a whole-universe cache producer ({#prewarm}'s tables), which asks about every known class;
|
|
2000
|
+
# - the hierarchy oracle's ancestry lookup ({#ancestor_names_for}), which asks whether two classes are
|
|
2001
|
+
# ordered, not whether either one's methods resolve.
|
|
2002
|
+
#
|
|
2003
|
+
# Both were cache-state-dependent before this, and each produced a DIFFERENT class list per
|
|
2004
|
+
# configuration for the same project — the second one on the DEFAULT `--workers=0` path (issue #696
|
|
2005
|
+
# review, second pass).
|
|
2006
|
+
#
|
|
2007
|
+
# Save-and-restore rather than a bare flag: `#prewarm` wraps a body whose members wrap themselves, and
|
|
2008
|
+
# a nested demand must not un-mark its caller on the way out.
|
|
2009
|
+
#
|
|
2010
|
+
# Per LOADER, not per thread. Nesting and a raise mid-demand are both handled, and the fork pool forks
|
|
2011
|
+
# after `#prewarm` returns, so no CLI path shares a loader across concurrent analyses. An in-process
|
|
2012
|
+
# host that did (`language_server/debouncer.rb` runs analysis on a `Thread`) could have one analysis
|
|
2013
|
+
# silence another's reporting; not reachable today, and not worth a thread-local until it is.
|
|
2014
|
+
def during_internal_demand
|
|
2015
|
+
previous = @state[:internal_demand]
|
|
2016
|
+
@state[:internal_demand] = true
|
|
2017
|
+
yield
|
|
2018
|
+
ensure
|
|
2019
|
+
@state[:internal_demand] = previous
|
|
2020
|
+
end
|
|
2021
|
+
|
|
1573
2022
|
# The third twin of {#warn_about_quarantined_signatures} / {#warn_about_virtual_rbs_collisions}: name,
|
|
1574
2023
|
# once per class per PROCESS, a `RBS::DefinitionBuilder` failure caught in
|
|
1575
2024
|
# {#build_instance_definition} / {#build_singleton_definition}'s rescue. Without it, `class_known?`
|
|
@@ -1624,22 +2073,53 @@ module Rigor
|
|
|
1624
2073
|
# `#location`. Falls back to an empty list — the warning still names the class and the exception's own
|
|
1625
2074
|
# message — for the remainder (e.g. `SuperclassMismatchError`, which carries neither).
|
|
1626
2075
|
def definition_build_conflict_buffers(error)
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
elsif error.respond_to?(:member) && error.member
|
|
1631
|
-
[error.member.location]
|
|
1632
|
-
elsif error.respond_to?(:location)
|
|
1633
|
-
[error.location]
|
|
1634
|
-
else
|
|
1635
|
-
[]
|
|
1636
|
-
end
|
|
1637
|
-
|
|
1638
|
-
locations.compact.filter_map { |loc| loc.buffer&.name }.map(&:to_s).uniq.freeze
|
|
2076
|
+
definition_build_error_locations(error)
|
|
2077
|
+
.compact.filter_map { |loc| loc.buffer&.name }.map(&:to_s)
|
|
2078
|
+
.reject { |name| name == CACHED_LOCATION_BUFFER_NAME }.uniq.freeze
|
|
1639
2079
|
rescue ::RBS::BaseError, StandardError
|
|
1640
2080
|
[].freeze
|
|
1641
2081
|
end
|
|
1642
2082
|
|
|
2083
|
+
# The `RBS::Location`s an error carries, by whichever accessor its class happens to expose (see the
|
|
2084
|
+
# shape survey above). Split out so the buffer-name extraction reads as one pipeline.
|
|
2085
|
+
def definition_build_error_locations(error)
|
|
2086
|
+
return Array(error.members).map(&:location) if error.respond_to?(:members) && error.members
|
|
2087
|
+
return [error.member.location] if error.respond_to?(:member) && error.member
|
|
2088
|
+
return [error.location] if error.respond_to?(:location)
|
|
2089
|
+
|
|
2090
|
+
[]
|
|
2091
|
+
end
|
|
2092
|
+
|
|
2093
|
+
# The member the failure is ABOUT, taken off the error object rather than parsed out of its message.
|
|
2094
|
+
#
|
|
2095
|
+
# Issue #696 — the message cannot serve here. It is built from `RBS::Location`s, and the ADR-54 env
|
|
2096
|
+
# cache drops their POSITIONS, so a warm run's message would name no line and the same project would
|
|
2097
|
+
# report different text cold and warm. These accessors are `RBS::TypeName`s and Symbols, so they
|
|
2098
|
+
# survive the marshal round trip unchanged — and they name the class that actually CARRIES the
|
|
2099
|
+
# duplicate, which the failed-class list does not: a collision on `::Object#blank?` fails `String`,
|
|
2100
|
+
# `Integer` and `Array`, none of which is where the fix goes. (The buffer NAME does survive, since the
|
|
2101
|
+
# second review pass; it is the file list that uses it, not this.)
|
|
2102
|
+
#
|
|
2103
|
+
# Shapes vary by error class (`references/rbs`'s `lib/rbs/errors.rb`): the two duplicated-definition
|
|
2104
|
+
# errors expose `#qualified_method_name` directly; `InvalidOverloadMethodError` has `#type_name` +
|
|
2105
|
+
# `#method_name`; `UnknownMethodAliasError` and `NoSuperclassFoundError` have `#type_name`;
|
|
2106
|
+
# `SuperclassMismatchError` has `#name`. `RecursiveAncestorError` has none of them and yields nil, and
|
|
2107
|
+
# the diagnostic then omits the clause rather than inventing one.
|
|
2108
|
+
#
|
|
2109
|
+
# @return [String, nil]
|
|
2110
|
+
def definition_build_member(error)
|
|
2111
|
+
return error.qualified_method_name.to_s if error.respond_to?(:qualified_method_name)
|
|
2112
|
+
|
|
2113
|
+
type_name = error.respond_to?(:type_name) ? error.type_name : nil
|
|
2114
|
+
type_name ||= error.respond_to?(:name) ? error.name : nil
|
|
2115
|
+
return nil if type_name.nil?
|
|
2116
|
+
|
|
2117
|
+
method_name = error.respond_to?(:method_name) ? error.method_name : nil
|
|
2118
|
+
method_name.nil? ? type_name.to_s : "#{type_name}##{method_name}"
|
|
2119
|
+
rescue ::RBS::BaseError, StandardError
|
|
2120
|
+
nil
|
|
2121
|
+
end
|
|
2122
|
+
|
|
1643
2123
|
# Resolve an RBS class/module ALIAS to its canonical declared name. `class Mutex = Thread::Mutex`
|
|
1644
2124
|
# lives only in `class_alias_decls`, so `class_known?` reports it (it checks that table) but the
|
|
1645
2125
|
# definition builder — which only knows `class_decls` — could not enumerate its methods, leaving alias
|
|
@@ -1688,6 +2168,5 @@ module Rigor
|
|
|
1688
2168
|
false
|
|
1689
2169
|
end
|
|
1690
2170
|
end
|
|
1691
|
-
# rubocop:enable Metrics/ClassLength
|
|
1692
2171
|
end
|
|
1693
2172
|
end
|