rigortype 0.1.19 → 0.2.1
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 +41 -6
- data/data/core_overlay/numeric.rbs +33 -0
- data/data/core_overlay/pathname.rbs +25 -0
- data/data/core_overlay/string_scanner.rbs +28 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
- data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
- data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
- data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
- data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
- data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
- data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
- data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
- data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
- data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
- data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
- data/data/vendored_gem_sigs/redis/future.rbs +5 -0
- data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
- data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +3 -21
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules.rb +492 -71
- data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
- data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
- data/lib/rigor/analysis/fact_store.rb +5 -4
- data/lib/rigor/analysis/rule_catalog.rb +153 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +17 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +9 -8
- data/lib/rigor/analysis/runner.rb +17 -6
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +10 -14
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +28 -7
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +138 -16
- data/lib/rigor/cli/coverage_command.rb +138 -31
- data/lib/rigor/cli/coverage_mutation.rb +149 -0
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -0
- data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
- data/lib/rigor/cli/fused_protection_report.rb +76 -0
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
- data/lib/rigor/cli/mutation_protection_report.rb +73 -0
- data/lib/rigor/cli/options.rb +9 -0
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +2 -1
- data/lib/rigor/cli/triage_command.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_command.rb +2 -1
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +152 -0
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration.rb +57 -7
- data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
- data/lib/rigor/environment/class_registry.rb +4 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
- data/lib/rigor/environment/lockfile_resolver.rb +1 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
- data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
- data/lib/rigor/environment/rbs_loader.rb +76 -5
- data/lib/rigor/environment.rb +66 -8
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution.rb +3 -5
- data/lib/rigor/inference/acceptance.rb +17 -9
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
- data/lib/rigor/inference/expression_typer.rb +20 -28
- data/lib/rigor/inference/hkt_body.rb +8 -11
- data/lib/rigor/inference/hkt_body_parser.rb +10 -12
- data/lib/rigor/inference/hkt_registry.rb +10 -11
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +169 -24
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +90 -15
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +40 -48
- data/lib/rigor/inference/mutation_widening.rb +5 -11
- data/lib/rigor/inference/narrowing.rb +14 -16
- data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
- data/lib/rigor/inference/project_patched_methods.rb +4 -7
- data/lib/rigor/inference/project_patched_scanner.rb +2 -13
- data/lib/rigor/inference/protection_scanner.rb +86 -0
- data/lib/rigor/inference/scope_indexer.rb +129 -55
- data/lib/rigor/inference/statement_evaluator.rb +271 -114
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/language_server/completion_provider.rb +6 -12
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
- data/lib/rigor/language_server/hover_provider.rb +2 -3
- data/lib/rigor/language_server/hover_renderer.rb +2 -11
- data/lib/rigor/language_server/server.rb +9 -17
- data/lib/rigor/language_server.rb +4 -5
- data/lib/rigor/plugin/base.rb +10 -8
- data/lib/rigor/plugin/macro/block_as_method.rb +3 -4
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +4 -5
- data/lib/rigor/plugin/manifest.rb +45 -66
- data/lib/rigor/plugin/registry.rb +6 -7
- data/lib/rigor/plugin/type_node_resolver.rb +6 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
- data/lib/rigor/protection/mutation_scanner.rb +180 -0
- data/lib/rigor/protection/mutator.rb +267 -0
- data/lib/rigor/protection/test_suite_oracle.rb +68 -0
- data/lib/rigor/rbs_extended.rb +24 -36
- data/lib/rigor/reflection.rb +4 -7
- data/lib/rigor/scope/discovery_index.rb +14 -2
- data/lib/rigor/scope.rb +54 -11
- data/lib/rigor/sig_gen/observed_call.rb +3 -3
- data/lib/rigor/sig_gen/writer.rb +40 -2
- data/lib/rigor/signature_path_audit.rb +92 -0
- data/lib/rigor/source/constant_path.rb +62 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +16 -3
- data/lib/rigor/type/constant.rb +2 -11
- data/lib/rigor/type/data_class.rb +2 -11
- data/lib/rigor/type/data_instance.rb +2 -11
- data/lib/rigor/type/hash_shape.rb +2 -11
- data/lib/rigor/type/integer_range.rb +2 -11
- data/lib/rigor/type/intersection.rb +2 -11
- data/lib/rigor/type/nominal.rb +2 -11
- data/lib/rigor/type/plain_lattice.rb +37 -0
- data/lib/rigor/type/refined.rb +72 -13
- data/lib/rigor/type/singleton.rb +2 -11
- data/lib/rigor/type/struct_class.rb +75 -0
- data/lib/rigor/type/struct_instance.rb +93 -0
- data/lib/rigor/type/tuple.rb +5 -15
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +3 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +7 -10
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +6 -8
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -2
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +7 -9
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +3 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +19 -14
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +28 -41
- data/sig/rigor/scope.rbs +9 -1
- data/sig/rigor/type.rbs +36 -1
- metadata +49 -1
data/lib/rigor/configuration.rb
CHANGED
|
@@ -233,6 +233,9 @@ module Rigor
|
|
|
233
233
|
# included files first (in declaration order), then the
|
|
234
234
|
# current file's own keys override. Relative paths inside
|
|
235
235
|
# each file are resolved against that file's directory.
|
|
236
|
+
# Public so the CLI can run the include-aware load before
|
|
237
|
+
# applying `--treat-all-as-inline-rbs`'s plugin injection
|
|
238
|
+
# (see {CLI::CheckCommand#load_check_configuration}).
|
|
236
239
|
def self.load_with_includes(path, visited: Set.new)
|
|
237
240
|
absolute = File.expand_path(path)
|
|
238
241
|
raise ArgumentError, "circular include: #{absolute}" if visited.include?(absolute)
|
|
@@ -328,7 +331,7 @@ module Rigor
|
|
|
328
331
|
out["source_inference"] = left_si + right_si unless both_empty # rigor:disable flow.always-truthy-condition
|
|
329
332
|
out
|
|
330
333
|
end
|
|
331
|
-
private_class_method :
|
|
334
|
+
private_class_method :merge_includes, :resolve_paths_in, :deep_merge,
|
|
332
335
|
:merge_value, :merge_dependencies_hash
|
|
333
336
|
|
|
334
337
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
@@ -437,6 +440,32 @@ module Rigor
|
|
|
437
440
|
}
|
|
438
441
|
end
|
|
439
442
|
|
|
443
|
+
# ADR-50 § WD2 — returns a sibling Configuration whose bleeding-edge
|
|
444
|
+
# selection (and the derived `bleeding_edge_severity_overrides` the two
|
|
445
|
+
# {SeverityProfile.resolve} sites consult) is replaced by `value`,
|
|
446
|
+
# leaving every other field shared with the receiver. `value` takes the
|
|
447
|
+
# same forms as the `bleeding_edge:` config key — `false` / `true` / a
|
|
448
|
+
# feature-id Array / `{ "all" => true, "except" => [...] }` — and is
|
|
449
|
+
# normalised through the same {#coerce_bleeding_edge} path, so an unknown
|
|
450
|
+
# id stays inert.
|
|
451
|
+
#
|
|
452
|
+
# The CLI's `--bleeding-edge[=ids]` / `--no-bleeding-edge` flag uses this
|
|
453
|
+
# to override the configured selection for a single run (the same
|
|
454
|
+
# CLI-over-config precedence `--workers` and `--no-cache` follow). It is a
|
|
455
|
+
# frozen `dup` with the two bleeding-edge ivars re-set: `dup` returns an
|
|
456
|
+
# unfrozen shallow copy (every other ivar is the receiver's deeply-frozen
|
|
457
|
+
# value, safe to share read-only), the two replacements are themselves
|
|
458
|
+
# deeply frozen, and the re-`freeze` keeps the result `Ractor.shareable?`
|
|
459
|
+
# for the worker path.
|
|
460
|
+
def with_bleeding_edge(value)
|
|
461
|
+
selector = coerce_bleeding_edge(value)
|
|
462
|
+
copy = dup
|
|
463
|
+
copy.instance_variable_set(:@bleeding_edge, selector)
|
|
464
|
+
copy.instance_variable_set(:@bleeding_edge_severity_overrides,
|
|
465
|
+
BleedingEdge.severity_overrides_for(selector))
|
|
466
|
+
copy.freeze
|
|
467
|
+
end
|
|
468
|
+
|
|
440
469
|
private
|
|
441
470
|
|
|
442
471
|
# ADR-17 slice 4 — `pre_eval:` glob expansion. Each entry is
|
|
@@ -499,10 +528,9 @@ module Rigor
|
|
|
499
528
|
s.dup.freeze
|
|
500
529
|
end
|
|
501
530
|
|
|
502
|
-
#
|
|
503
|
-
#
|
|
504
|
-
#
|
|
505
|
-
# downstream `TrustPolicy` constructor stays strict.
|
|
531
|
+
# YAML scalar may arrive as a String or already as a Symbol;
|
|
532
|
+
# coerce to the canonical Symbol shape so the downstream
|
|
533
|
+
# `TrustPolicy` constructor stays strict.
|
|
506
534
|
#
|
|
507
535
|
# The accepted set is duplicated from
|
|
508
536
|
# {Rigor::Plugin::TrustPolicy::VALID_NETWORK_POLICIES} so
|
|
@@ -572,6 +600,18 @@ module Rigor
|
|
|
572
600
|
raise ArgumentError, "severity_overrides must be a Hash, got #{value.inspect}" unless value.is_a?(Hash)
|
|
573
601
|
|
|
574
602
|
value.to_h do |k, v|
|
|
603
|
+
# YAML 1.1 parses bare `off`/`on`/`no`/`yes`/`true`/`false`
|
|
604
|
+
# as booleans, so a user who wrote `off` (a valid severity)
|
|
605
|
+
# without quotes hands us `false`. Catch the non-Symbol /
|
|
606
|
+
# non-String case before `to_sym` blows up with a backtrace.
|
|
607
|
+
unless v.is_a?(String) || v.is_a?(Symbol)
|
|
608
|
+
hint = v == false ? %( — did you mean the string "off"?) : ""
|
|
609
|
+
raise ArgumentError,
|
|
610
|
+
"severity_overrides[#{k.inspect}] is #{v.inspect}, a YAML boolean#{hint} " \
|
|
611
|
+
"Bare off/on/no/yes/true/false are parsed as booleans; quote the severity " \
|
|
612
|
+
"(e.g. \"off\")."
|
|
613
|
+
end
|
|
614
|
+
|
|
575
615
|
sym = v.to_sym
|
|
576
616
|
unless SeverityProfile::VALID_SEVERITIES.include?(sym)
|
|
577
617
|
raise ArgumentError,
|
|
@@ -593,7 +633,7 @@ module Rigor
|
|
|
593
633
|
case value
|
|
594
634
|
when nil, false then { "mode" => "none" }
|
|
595
635
|
when true then { "mode" => "all" }
|
|
596
|
-
when Array then { "mode" => "list", "ids" => value
|
|
636
|
+
when Array then { "mode" => "list", "ids" => freeze_ids(value) }
|
|
597
637
|
when Hash then coerce_bleeding_edge_hash(value)
|
|
598
638
|
else
|
|
599
639
|
raise ArgumentError,
|
|
@@ -605,12 +645,22 @@ module Rigor
|
|
|
605
645
|
def coerce_bleeding_edge_hash(value)
|
|
606
646
|
hash = value.to_h { |k, v| [k.to_s, v] }
|
|
607
647
|
if hash.fetch("all", false) == true
|
|
608
|
-
{ "mode" => "all", "except" => Array(hash["except"])
|
|
648
|
+
{ "mode" => "all", "except" => freeze_ids(Array(hash["except"])) }
|
|
609
649
|
else
|
|
610
650
|
{ "mode" => "none" }
|
|
611
651
|
end
|
|
612
652
|
end
|
|
613
653
|
|
|
654
|
+
# Feature ids reach `coerce_bleeding_edge` from YAML, a `to_h` round-trip,
|
|
655
|
+
# or the CLI's `--bleeding-edge=a,b` (all runtime-created, non-frozen
|
|
656
|
+
# Strings). Each id String is frozen so the normalized selector — and thus
|
|
657
|
+
# the whole Configuration — stays deeply frozen and `Ractor.shareable?`
|
|
658
|
+
# for the worker path (the same invariant `#initialize`'s final `freeze`
|
|
659
|
+
# upholds for every other field).
|
|
660
|
+
def freeze_ids(ids)
|
|
661
|
+
ids.map { |id| id.to_s.dup.freeze }.freeze
|
|
662
|
+
end
|
|
663
|
+
|
|
614
664
|
# Renders the normalized selector back into the user-facing
|
|
615
665
|
# `bleeding_edge:` form for `#to_h` round-trips.
|
|
616
666
|
def bleeding_edge_to_h
|
|
@@ -4,7 +4,7 @@ require "yaml"
|
|
|
4
4
|
|
|
5
5
|
module Rigor
|
|
6
6
|
class Environment
|
|
7
|
-
#
|
|
7
|
+
# Target-project Bundler awareness (O4, implemented).
|
|
8
8
|
#
|
|
9
9
|
# Walks a Bundler-installed gem tree (e.g., the project's
|
|
10
10
|
# `vendor/bundle` or a Docker-mounted bundle root) and
|
|
@@ -84,11 +84,12 @@ module Rigor
|
|
|
84
84
|
# supplied) minus any whose `(name, version, platform)`
|
|
85
85
|
# does not match a lockfile entry.
|
|
86
86
|
def self.discover(bundle_path:, project_root: Dir.pwd, auto_detect: true,
|
|
87
|
-
skip_gems: SKIPPED_GEMS_BY_DEFAULT, locked_gems: nil)
|
|
87
|
+
skip_gems: SKIPPED_GEMS_BY_DEFAULT, locked_gems: nil, home: nil)
|
|
88
88
|
resolved = resolve_bundle_path(
|
|
89
89
|
bundle_path: bundle_path,
|
|
90
90
|
project_root: project_root,
|
|
91
|
-
auto_detect: auto_detect
|
|
91
|
+
auto_detect: auto_detect,
|
|
92
|
+
home: home
|
|
92
93
|
)
|
|
93
94
|
return [] if resolved.nil?
|
|
94
95
|
|
|
@@ -143,7 +144,7 @@ module Rigor
|
|
|
143
144
|
# Returns `Pathname` resolved bundle path, or `nil` when
|
|
144
145
|
# neither explicit nor auto-detected. Public for the stats
|
|
145
146
|
# banner so end users can see what rigor picked up.
|
|
146
|
-
def self.resolve_bundle_path(bundle_path:, project_root: Dir.pwd, auto_detect: true)
|
|
147
|
+
def self.resolve_bundle_path(bundle_path:, project_root: Dir.pwd, auto_detect: true, home: nil)
|
|
147
148
|
if bundle_path
|
|
148
149
|
path = Pathname.new(File.expand_path(bundle_path.to_s, project_root))
|
|
149
150
|
return path if path.directory?
|
|
@@ -153,31 +154,77 @@ module Rigor
|
|
|
153
154
|
|
|
154
155
|
return nil unless auto_detect
|
|
155
156
|
|
|
156
|
-
detected = auto_detect(project_root: project_root)
|
|
157
|
+
detected = auto_detect(project_root: project_root, home: home)
|
|
157
158
|
Pathname.new(detected) if detected
|
|
158
159
|
end
|
|
159
160
|
|
|
160
|
-
# Auto-detection order
|
|
161
|
+
# Auto-detection order — project-local strategies win over the
|
|
162
|
+
# user-global one, mirroring Bundler's own config precedence:
|
|
161
163
|
# 1. `<project_root>/.bundle/config` carries `BUNDLE_PATH:`
|
|
162
164
|
# set by `bundle config set --local path <dir>`.
|
|
163
165
|
# 2. `<project_root>/vendor/bundle/` — the conventional
|
|
164
166
|
# in-tree install location when a developer ran
|
|
165
167
|
# `bundle install --path vendor/bundle`.
|
|
166
|
-
# 3.
|
|
168
|
+
# 3. The user-global bundler config `<home>/.bundle/config`
|
|
169
|
+
# `BUNDLE_PATH:` (`bundle config set --global path <dir>`),
|
|
170
|
+
# resolved relative to the project root and used only when
|
|
171
|
+
# it points at an existing directory — the last resort for
|
|
172
|
+
# a project with no in-tree bundle. Purely additive: it is
|
|
173
|
+
# consulted only when steps 1–2 found nothing, so it never
|
|
174
|
+
# changes an already-working detection.
|
|
175
|
+
# 4. `nil` — let the caller proceed without bundle sig
|
|
167
176
|
# discovery (rigor's vendored RBS still loads).
|
|
168
|
-
|
|
169
|
-
|
|
177
|
+
#
|
|
178
|
+
# Note (ADR-27): rigor reads the *project* as data, so
|
|
179
|
+
# detection is limited to paths recorded in project-local or
|
|
180
|
+
# user-global Bundler config files. The pure-default install
|
|
181
|
+
# location — gems in the active Ruby's GEM_HOME with no `path`
|
|
182
|
+
# configured — is the *project's* Ruby's gem home, which the
|
|
183
|
+
# isolated analyzer cannot know without running the project's
|
|
184
|
+
# toolchain. Point rigor at it with `bundler.bundle_path:`, or
|
|
185
|
+
# supply signatures via `rbs collection install` /
|
|
186
|
+
# `dependencies.source_inference:`. `BUNDLE_PATH` from rigor's
|
|
187
|
+
# own environment is deliberately NOT consulted — it describes
|
|
188
|
+
# rigor's bundle, not the analyzed project's.
|
|
189
|
+
#
|
|
190
|
+
# `home:` defaults to the invoking user's home directory; it is
|
|
191
|
+
# a parameter so tests stay hermetic (no read of the real
|
|
192
|
+
# `~/.bundle/config`).
|
|
193
|
+
def self.auto_detect(project_root:, home: nil)
|
|
194
|
+
from_config = read_bundle_config_path(File.join(project_root, ".bundle", "config"))
|
|
170
195
|
return File.expand_path(from_config, project_root) if from_config
|
|
171
196
|
|
|
172
197
|
vendor = File.join(project_root, "vendor", "bundle")
|
|
173
198
|
return vendor if File.directory?(vendor)
|
|
174
199
|
|
|
200
|
+
global_bundle_path(project_root: project_root, home: home)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Resolves the user-global `BUNDLE_PATH` against the project
|
|
204
|
+
# root, returning it only when it is an existing directory.
|
|
205
|
+
# Returns nil when there is no global config, no home, or the
|
|
206
|
+
# configured path does not exist.
|
|
207
|
+
def self.global_bundle_path(project_root:, home:)
|
|
208
|
+
home ||= default_home
|
|
209
|
+
return nil if home.nil?
|
|
210
|
+
|
|
211
|
+
configured = read_bundle_config_path(File.join(home, ".bundle", "config"))
|
|
212
|
+
return nil unless configured
|
|
213
|
+
|
|
214
|
+
resolved = File.expand_path(configured, project_root)
|
|
215
|
+
File.directory?(resolved) ? resolved : nil
|
|
216
|
+
end
|
|
217
|
+
private_class_method :global_bundle_path
|
|
218
|
+
|
|
219
|
+
def self.default_home
|
|
220
|
+
Dir.home
|
|
221
|
+
rescue StandardError
|
|
175
222
|
nil
|
|
176
223
|
end
|
|
224
|
+
private_class_method :default_home
|
|
177
225
|
|
|
178
|
-
def self.read_bundle_config_path(
|
|
179
|
-
config_path
|
|
180
|
-
return nil unless File.exist?(config_path)
|
|
226
|
+
def self.read_bundle_config_path(config_path)
|
|
227
|
+
return nil unless config_path && File.exist?(config_path)
|
|
181
228
|
|
|
182
229
|
# `.bundle/config` is YAML with all-caps env-style keys.
|
|
183
230
|
# `BUNDLE_PATH:` is the canonical key (Bundler 2.x); the
|
|
@@ -185,7 +232,8 @@ module Rigor
|
|
|
185
232
|
data = YAML.safe_load_file(config_path)
|
|
186
233
|
return nil unless data.is_a?(Hash)
|
|
187
234
|
|
|
188
|
-
data["BUNDLE_PATH"]
|
|
235
|
+
path = data["BUNDLE_PATH"]
|
|
236
|
+
path && !path.to_s.empty? ? path.to_s : nil
|
|
189
237
|
rescue StandardError
|
|
190
238
|
# Malformed `.bundle/config` should not break analysis;
|
|
191
239
|
# silently skip auto-detection.
|
|
@@ -7,8 +7,8 @@ module Rigor
|
|
|
7
7
|
# Resolves Ruby Class/Module objects to Rigor::Type::Nominal instances.
|
|
8
8
|
# The hardcoded list spans the core classes the literal typer (Slice 1)
|
|
9
9
|
# and the constant-resolution path (Slice 2 strengthening) need.
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# This is the static fast path for built-ins; `Environment` falls through
|
|
11
|
+
# to `RbsLoader` for all other names — the two tiers are complementary.
|
|
12
12
|
#
|
|
13
13
|
# See docs/internal-spec/inference-engine.md for the binding contract
|
|
14
14
|
# (every entry below MUST always be recognised).
|
|
@@ -28,7 +28,8 @@ module Rigor
|
|
|
28
28
|
# Common Ruby core classes that user code routinely names by constant
|
|
29
29
|
# reference. Adding them to the registry lets `nominal_for_name`
|
|
30
30
|
# resolve `Array`, `Hash`, etc. without each call site re-listing
|
|
31
|
-
# them
|
|
31
|
+
# them. The static registry is the cheap fast path; `RbsLoader`
|
|
32
|
+
# extends coverage to all other RBS-declared names (Slice 4, landed).
|
|
32
33
|
SLICE_2_BUILT_INS = [
|
|
33
34
|
Array,
|
|
34
35
|
Hash,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
class Environment
|
|
5
|
+
# Per-key memoization container for {Environment#constant_for_name}.
|
|
6
|
+
# Held by {Environment} so the otherwise-frozen instance can cache
|
|
7
|
+
# constant-resolution results on first access — the sibling of
|
|
8
|
+
# {HktRegistryHolder}, generalised from one slot to a name-keyed map.
|
|
9
|
+
#
|
|
10
|
+
# Why this exists: `constant_for_name` is a pure function of its
|
|
11
|
+
# name for a given Environment (both the predefined-constant
|
|
12
|
+
# refinement table and the RBS loader's constant table are fixed for
|
|
13
|
+
# the Environment's lifetime), yet it is the hottest non-dispatch
|
|
14
|
+
# path on a large Rails app. {ExpressionTyper#resolve_constant_name}
|
|
15
|
+
# peels a lexical-candidate ladder per constant reference, so the
|
|
16
|
+
# same qualified names recur thousands of times across files — and
|
|
17
|
+
# each miss runs a `const_get` walk that raises + rescues `NameError`
|
|
18
|
+
# for every project-defined constant (the common case). Caching the
|
|
19
|
+
# result collapses the repeats to a hash lookup.
|
|
20
|
+
#
|
|
21
|
+
# Caches `nil` results too (a name that resolves to no refined /
|
|
22
|
+
# RBS constant type stays unresolved for the run), so the
|
|
23
|
+
# const_get-and-rescue is paid at most once per distinct name.
|
|
24
|
+
#
|
|
25
|
+
# Concurrency: single-threaded use only, the same discipline as
|
|
26
|
+
# {HktRegistryHolder} — the Ractor pool builds a per-worker
|
|
27
|
+
# Environment and the LSP single-publish invariant serialises the
|
|
28
|
+
# shared-Environment reader path. If a future caller introduces a
|
|
29
|
+
# multi-threaded reader against one Environment, the synchronisation
|
|
30
|
+
# belongs at that caller's seam, not here.
|
|
31
|
+
class ConstantTypeCacheHolder
|
|
32
|
+
def initialize
|
|
33
|
+
@cache = {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def fetch(key)
|
|
37
|
+
return @cache[key] if @cache.key?(key)
|
|
38
|
+
|
|
39
|
+
@cache[key] = yield
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
class Environment
|
|
5
|
-
#
|
|
5
|
+
# Gemfile.lock parser for target-project bundler awareness (O4 Layer 3, implemented).
|
|
6
6
|
#
|
|
7
7
|
# Parses a target project's `Gemfile.lock` via Bundler's
|
|
8
8
|
# `LockfileParser` and exposes the locked gem set as a frozen
|
|
@@ -4,8 +4,7 @@ require "yaml"
|
|
|
4
4
|
|
|
5
5
|
module Rigor
|
|
6
6
|
class Environment
|
|
7
|
-
#
|
|
8
|
-
# awareness.
|
|
7
|
+
# `rbs collection install` awareness (O4 Layer 3 slice 2, implemented).
|
|
9
8
|
#
|
|
10
9
|
# When the target project has been set up with `rbs
|
|
11
10
|
# collection install` (the standard RBS-ecosystem flow for
|
|
@@ -42,7 +42,8 @@ module Rigor
|
|
|
42
42
|
# enough that hard-coding is acceptable; a directory walk
|
|
43
43
|
# at every call would add stat-cost to no benefit.)
|
|
44
44
|
VENDORED_GEM_NAMES = Set[
|
|
45
|
-
"bcrypt", "bundler", "cgi", "did_you_mean",
|
|
45
|
+
"ast", "bcrypt", "bundler", "cgi", "did_you_mean",
|
|
46
|
+
"idn-ruby", "mysql2", "nokogiri", "pg", "prism", "redis", "rubygems"
|
|
46
47
|
].freeze
|
|
47
48
|
|
|
48
49
|
# @param locked_gems [Hash{String => LockfileResolver::LockedGem}]
|
|
@@ -323,6 +323,33 @@ module Rigor
|
|
|
323
323
|
[Pathname(CORE_OVERLAY_SIGS_ROOT)]
|
|
324
324
|
end
|
|
325
325
|
|
|
326
|
+
# Rigor-owned per-gem RBS overlays (`data/gem_overlay/<gem>/`),
|
|
327
|
+
# ADR-72. Unlike the unconditional `core_overlay`, each gem's
|
|
328
|
+
# overlay is loaded ONLY when that gem is locked in the
|
|
329
|
+
# project's Gemfile.lock but ships no RBS of its own —
|
|
330
|
+
# {Environment.for_project} decides eligibility and passes the
|
|
331
|
+
# already-filtered gem-name set here. One directory per gem name
|
|
332
|
+
# keeps the membership check a cheap `File.directory?`.
|
|
333
|
+
GEM_OVERLAY_SIGS_ROOT = File.expand_path(
|
|
334
|
+
"../../../data/gem_overlay",
|
|
335
|
+
__dir__
|
|
336
|
+
).freeze
|
|
337
|
+
|
|
338
|
+
# @param gem_names [Enumerable<String>] overlay-eligible
|
|
339
|
+
# Gemfile.lock gem names (the caller filters to the
|
|
340
|
+
# `:missing`-coverage, no-conflicting-plugin set).
|
|
341
|
+
# @return [Array<Pathname>] the bundled overlay directory for
|
|
342
|
+
# each gem that ships one; empty when none match or the
|
|
343
|
+
# overlay root is absent.
|
|
344
|
+
def gem_overlay_sig_paths(gem_names)
|
|
345
|
+
return [] unless File.directory?(GEM_OVERLAY_SIGS_ROOT)
|
|
346
|
+
|
|
347
|
+
gem_names.filter_map do |name|
|
|
348
|
+
dir = File.join(GEM_OVERLAY_SIGS_ROOT, name.to_s)
|
|
349
|
+
Pathname(dir) if File.directory?(dir)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
326
353
|
def vendored_gem_sig_paths
|
|
327
354
|
return [] unless File.directory?(VENDORED_GEM_SIGS_ROOT)
|
|
328
355
|
|
|
@@ -363,11 +390,11 @@ module Rigor
|
|
|
363
390
|
# out at build time so the loader stays robust to fixtures and
|
|
364
391
|
# bare repositories.
|
|
365
392
|
# @param cache_store [Rigor::Cache::Store, nil] the persistent
|
|
366
|
-
# cache the loader
|
|
367
|
-
#
|
|
368
|
-
#
|
|
369
|
-
#
|
|
370
|
-
# through here when
|
|
393
|
+
# cache the loader threads through to `RbsEnvironment`,
|
|
394
|
+
# `RbsKnownClassNames`, `RbsConstantTable`,
|
|
395
|
+
# `RbsClassAncestorTable`, and `RbsClassTypeParamNames`
|
|
396
|
+
# producers. Pass `nil` (the default) to skip caching; the
|
|
397
|
+
# runner threads its own Store through here when enabled.
|
|
371
398
|
# @param virtual_rbs [Array<[String, String]>] ADR-32 WD4 —
|
|
372
399
|
# `[virtual_filename, rbs_source]` pairs synthesised from
|
|
373
400
|
# project source by a plugin's
|
|
@@ -643,6 +670,29 @@ module Rigor
|
|
|
643
670
|
interface_definition(interface_name)&.methods&.keys
|
|
644
671
|
end
|
|
645
672
|
|
|
673
|
+
# @param rbs_alias [RBS::Types::Alias] a type-alias reference (`string`,
|
|
674
|
+
# `int`, `range[int?]`, …) appearing in a method signature.
|
|
675
|
+
# @return [RBS::Types::t, nil] the alias's aliased type one level out,
|
|
676
|
+
# with type arguments substituted for a generic alias (`string` →
|
|
677
|
+
# `::String | ::_ToStr`; `range[int?]` → `::Range[int?] |
|
|
678
|
+
# ::_Range[int?]`), or nil for an unresolved name. Lets a caller see
|
|
679
|
+
# through the alias that {Inference::RbsTypeTranslator} otherwise
|
|
680
|
+
# degrades to `untyped`, which is why an interface/alias parameter
|
|
681
|
+
# does not reject `nil`. `expand_alias2` handles the (rarer) generic
|
|
682
|
+
# case — a `range[T]` param previously fell back to "admits", which
|
|
683
|
+
# suppressed e.g. `MatchData#[](nil)`.
|
|
684
|
+
def expand_type_alias(rbs_alias)
|
|
685
|
+
return nil if env.nil?
|
|
686
|
+
|
|
687
|
+
name = rbs_alias.name
|
|
688
|
+
name = name.absolute! unless name.absolute?
|
|
689
|
+
return nil unless env.type_alias_decls.key?(name)
|
|
690
|
+
|
|
691
|
+
builder.expand_alias2(name, rbs_alias.args)
|
|
692
|
+
rescue ::RBS::BaseError, StandardError
|
|
693
|
+
nil
|
|
694
|
+
end
|
|
695
|
+
|
|
646
696
|
# @return [RBS::Definition, nil] the resolved singleton (class
|
|
647
697
|
# object) definition for `class_name`. The methods on this
|
|
648
698
|
# definition are the *class methods* of `class_name`, including
|
|
@@ -1034,6 +1084,8 @@ module Rigor
|
|
|
1034
1084
|
rbs_name = parse_type_name(class_name)
|
|
1035
1085
|
return nil unless rbs_name
|
|
1036
1086
|
return nil if env.nil?
|
|
1087
|
+
|
|
1088
|
+
rbs_name = canonical_module_name(rbs_name)
|
|
1037
1089
|
return nil unless env.class_decls.key?(rbs_name)
|
|
1038
1090
|
|
|
1039
1091
|
builder.build_instance(rbs_name)
|
|
@@ -1045,6 +1097,8 @@ module Rigor
|
|
|
1045
1097
|
rbs_name = parse_type_name(class_name)
|
|
1046
1098
|
return nil unless rbs_name
|
|
1047
1099
|
return nil if env.nil?
|
|
1100
|
+
|
|
1101
|
+
rbs_name = canonical_module_name(rbs_name)
|
|
1048
1102
|
return nil unless env.class_decls.key?(rbs_name)
|
|
1049
1103
|
|
|
1050
1104
|
builder.build_singleton(rbs_name)
|
|
@@ -1052,6 +1106,23 @@ module Rigor
|
|
|
1052
1106
|
nil
|
|
1053
1107
|
end
|
|
1054
1108
|
|
|
1109
|
+
# Resolve an RBS class/module ALIAS to its canonical declared name.
|
|
1110
|
+
# `class Mutex = Thread::Mutex` lives only in `class_alias_decls`, so
|
|
1111
|
+
# `class_known?` reports it (it checks that table) but the definition
|
|
1112
|
+
# builder — which only knows `class_decls` — could not enumerate its
|
|
1113
|
+
# methods, leaving alias classes (`Mutex`, and any `X = Y`) with no
|
|
1114
|
+
# resolvable method surface. Normalising via the env (RBS's own alias
|
|
1115
|
+
# resolution) before the `class_decls` guard fixes dispatch AND the
|
|
1116
|
+
# `call.undefined-method` existence check on them. A non-alias name, or
|
|
1117
|
+
# one that does not normalise, is returned unchanged.
|
|
1118
|
+
def canonical_module_name(rbs_name)
|
|
1119
|
+
return rbs_name unless env.class_alias_decls.key?(rbs_name)
|
|
1120
|
+
|
|
1121
|
+
env.normalize_module_name?(rbs_name) || rbs_name
|
|
1122
|
+
rescue ::RBS::BaseError
|
|
1123
|
+
rbs_name
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1055
1126
|
# Memoised on `@state` (the per-loader store also holding `:env` /
|
|
1056
1127
|
# `:builder`): `RBS::TypeName.parse` is a pure, deterministic
|
|
1057
1128
|
# function of the normalised string, and the `RBS::TypeName` it
|
data/lib/rigor/environment.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "environment/rbs_loader"
|
|
|
7
7
|
require_relative "environment/reflection"
|
|
8
8
|
require_relative "environment/reporters"
|
|
9
9
|
require_relative "environment/hkt_registry_holder"
|
|
10
|
+
require_relative "environment/constant_type_cache_holder"
|
|
10
11
|
require_relative "environment/bundle_sig_discovery"
|
|
11
12
|
require_relative "environment/lockfile_resolver"
|
|
12
13
|
require_relative "environment/rbs_collection_discovery"
|
|
@@ -15,12 +16,13 @@ require_relative "inference/synthetic_method_index"
|
|
|
15
16
|
require_relative "inference/project_patched_methods"
|
|
16
17
|
require_relative "inference/hkt_registry"
|
|
17
18
|
require_relative "builtins/hkt_builtins"
|
|
19
|
+
require_relative "builtins/predefined_constant_refinements"
|
|
18
20
|
require_relative "type_node/name_scope"
|
|
19
21
|
require_relative "type_node/resolver_chain"
|
|
20
22
|
|
|
21
23
|
module Rigor
|
|
22
24
|
# The engine's view of the type universe outside the current scope.
|
|
23
|
-
# Slice 1 only
|
|
25
|
+
# Slice 1 exposed only the class registry; Slice 4 added the RBS loader,
|
|
24
26
|
# which threads through ExpressionTyper and MethodDispatcher to type
|
|
25
27
|
# constant references and method calls that the literal-typer and
|
|
26
28
|
# constant-folding tiers cannot answer.
|
|
@@ -63,6 +65,13 @@ module Rigor
|
|
|
63
65
|
prism rbs
|
|
64
66
|
].freeze
|
|
65
67
|
|
|
68
|
+
# ADR-72 — a Gemfile.lock gem name mapped to the opt-in plugin id
|
|
69
|
+
# that ships the SAME core-ext RBS. When that plugin is loaded the
|
|
70
|
+
# auto-overlay for the gem stands down, so the two never both
|
|
71
|
+
# declare the methods (which would raise a duplicate-declaration
|
|
72
|
+
# error). Keyed on the gem name `RbsCoverageReport` reports.
|
|
73
|
+
GEM_OVERLAY_PLUGIN_IDS = { "activesupport" => "activesupport-core-ext" }.freeze
|
|
74
|
+
|
|
66
75
|
attr_reader :class_registry, :rbs_loader, :plugin_registry, :dependency_source_index,
|
|
67
76
|
:reporters, :name_scope,
|
|
68
77
|
:synthetic_method_index, :project_patched_methods
|
|
@@ -123,6 +132,7 @@ module Rigor
|
|
|
123
132
|
# build at all.
|
|
124
133
|
@hkt_registry_base = hkt_registry || Inference::HktRegistry::EMPTY
|
|
125
134
|
@hkt_registry_holder = HktRegistryHolder.new
|
|
135
|
+
@constant_type_cache = ConstantTypeCacheHolder.new
|
|
126
136
|
@name_scope = build_name_scope
|
|
127
137
|
freeze
|
|
128
138
|
end
|
|
@@ -288,7 +298,24 @@ module Rigor
|
|
|
288
298
|
# collection discovery. A duplicate-declaration conflict
|
|
289
299
|
# degrades through the same O7 failure-memo path.
|
|
290
300
|
plugin_sig_paths = plugin_registry ? plugin_registry.signature_paths.map(&:to_s) : []
|
|
291
|
-
|
|
301
|
+
# ADR-72 — Gemfile.lock-gated bundled RBS overlays. For each
|
|
302
|
+
# locked gem that ships no RBS through any resolution path
|
|
303
|
+
# (`:missing`) and has a Rigor-bundled overlay, load that
|
|
304
|
+
# overlay so its core-class extensions resolve (e.g.
|
|
305
|
+
# `Integer#minutes` on a Rails project) — turning a systematic
|
|
306
|
+
# false `call.undefined-method` into a no-op while a project
|
|
307
|
+
# WITHOUT the gem still sees the genuine diagnostic. Appended
|
|
308
|
+
# last so any RBS the project already supplies wins, and skipped
|
|
309
|
+
# for a gem whose opt-in plugin twin is loaded (no duplicate
|
|
310
|
+
# declaration). The paths ride in `loader_signature_paths`, so
|
|
311
|
+
# the env cache descriptor digests them for free.
|
|
312
|
+
overlay_paths = gem_overlay_paths(
|
|
313
|
+
locked: locked, default_libraries: merged_libraries,
|
|
314
|
+
bundle_sig_paths: gem_sig_paths, rbs_collection_paths: collection_paths,
|
|
315
|
+
plugin_registry: plugin_registry
|
|
316
|
+
)
|
|
317
|
+
loader_signature_paths = resolved_paths + plugin_sig_paths + gem_sig_paths +
|
|
318
|
+
collection_paths + overlay_paths
|
|
292
319
|
# ADR-32 WD4 + WD5 — invoke each loaded plugin's
|
|
293
320
|
# `source_rbs_synthesizer` once per project source file
|
|
294
321
|
# and collect non-nil `[filename, rbs_source]` pairs.
|
|
@@ -343,6 +370,30 @@ module Rigor
|
|
|
343
370
|
sig.directory? ? [sig] : []
|
|
344
371
|
end
|
|
345
372
|
|
|
373
|
+
# ADR-72 — resolve the bundled RBS overlay directories to load for
|
|
374
|
+
# this project. A gem is eligible when it is locked in the
|
|
375
|
+
# Gemfile.lock, classified `:missing` by {RbsCoverageReport}
|
|
376
|
+
# (no RBS through default-library / vendored / bundle-`sig/` /
|
|
377
|
+
# rbs-collection), and its conflicting opt-in plugin (if any) is
|
|
378
|
+
# not loaded. Returns `[Pathname]`, deterministically ordered, or
|
|
379
|
+
# `[]` when no lockfile / no eligible gem.
|
|
380
|
+
def gem_overlay_paths(locked:, default_libraries:, bundle_sig_paths:,
|
|
381
|
+
rbs_collection_paths:, plugin_registry:)
|
|
382
|
+
return [] if locked.empty?
|
|
383
|
+
|
|
384
|
+
missing = RbsCoverageReport.classify(
|
|
385
|
+
locked_gems: locked, default_libraries: default_libraries,
|
|
386
|
+
bundle_sig_paths: bundle_sig_paths, rbs_collection_paths: rbs_collection_paths
|
|
387
|
+
).select { |row| row.source == :missing }.map(&:gem_name)
|
|
388
|
+
|
|
389
|
+
loaded_ids = plugin_registry ? plugin_registry.ids.to_set : Set.new
|
|
390
|
+
eligible = missing.reject do |gem_name|
|
|
391
|
+
plugin_id = GEM_OVERLAY_PLUGIN_IDS[gem_name]
|
|
392
|
+
plugin_id && loaded_ids.include?(plugin_id)
|
|
393
|
+
end.sort
|
|
394
|
+
RbsLoader.gem_overlay_sig_paths(eligible)
|
|
395
|
+
end
|
|
396
|
+
|
|
346
397
|
# ADR-32 WD4 + WD5 — for each project source file, invoke
|
|
347
398
|
# every plugin-registered synthesizer once and collect
|
|
348
399
|
# non-nil returns. The returned array is `[[virtual_filename,
|
|
@@ -453,11 +504,11 @@ module Rigor
|
|
|
453
504
|
def invoke_synthesizer_safely(callable, path)
|
|
454
505
|
callable.call(path.to_s)
|
|
455
506
|
rescue StandardError
|
|
456
|
-
# WD6 fail-soft — a synthesizer that raises does NOT
|
|
457
|
-
#
|
|
458
|
-
# `source-rbs-synthesis-failed`
|
|
459
|
-
#
|
|
460
|
-
#
|
|
507
|
+
# WD6 fail-soft — a synthesizer that raises does NOT crash
|
|
508
|
+
# analysis. Unlike the `[:error, msg]` return path (which
|
|
509
|
+
# the runner surfaces as `source-rbs-synthesis-failed`),
|
|
510
|
+
# an unhandled raise is swallowed silently; the
|
|
511
|
+
# unexamined-raise channel is deliberately silent per WD6.
|
|
461
512
|
nil
|
|
462
513
|
end
|
|
463
514
|
end
|
|
@@ -504,7 +555,14 @@ module Rigor
|
|
|
504
555
|
def constant_for_name(name)
|
|
505
556
|
return nil if rbs_loader.nil?
|
|
506
557
|
|
|
507
|
-
|
|
558
|
+
# Pure function of `name` for this Environment's lifetime — the
|
|
559
|
+
# refinement table and the RBS constant table are both fixed — so
|
|
560
|
+
# memoise across the lexical-candidate ladder's heavy name reuse.
|
|
561
|
+
key = name.to_s
|
|
562
|
+
@constant_type_cache.fetch(key) do
|
|
563
|
+
Builtins::PredefinedConstantRefinements.lookup(key) ||
|
|
564
|
+
rbs_loader.constant_type(key)
|
|
565
|
+
end
|
|
508
566
|
end
|
|
509
567
|
|
|
510
568
|
# Returns true when the constant name is known to either the static
|
|
@@ -18,7 +18,7 @@ module Rigor
|
|
|
18
18
|
# (`Rigor::RbsExtended::PredicateEffect`).
|
|
19
19
|
# 3. RBS::Extended `assert*` directives
|
|
20
20
|
# (`Rigor::RbsExtended::AssertEffect`).
|
|
21
|
-
# 4.
|
|
21
|
+
# 4. Plugin contributions via `type_specifier` DSL (ADR-52).
|
|
22
22
|
#
|
|
23
23
|
# Each of those four carriers translates to / from Fact at
|
|
24
24
|
# its boundary; downstream of {Rigor::FlowContribution#to_element_list}
|
|
@@ -20,11 +20,9 @@ module Rigor
|
|
|
20
20
|
# contract — see ADR-2 § "Flow Contribution Bundle" for the binding
|
|
21
21
|
# definition.
|
|
22
22
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# plugin contribution merger in v0.1.0. Plugin authors should not
|
|
27
|
-
# rely on it.
|
|
23
|
+
# `to_element_list` and `Merger` are implemented; plugin authors
|
|
24
|
+
# should not depend on the `Element` shape — the bundle is the
|
|
25
|
+
# public contract.
|
|
28
26
|
class FlowContribution
|
|
29
27
|
# Provenance carries the metadata every contribution needs for
|
|
30
28
|
# diagnostic attribution and cache invalidation. `source_family`
|