rigortype 0.1.19 → 0.2.0
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/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 +115 -16
- data/lib/rigor/cli/coverage_command.rb +148 -16
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -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/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration.rb +45 -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 +49 -5
- data/lib/rigor/environment.rb +17 -7
- 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 +156 -21
- 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 +244 -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/mutation_scanner.rb +120 -0
- data/lib/rigor/protection/mutator.rb +246 -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/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 +19 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "optionparser"
|
|
4
4
|
|
|
5
5
|
require_relative "../configuration"
|
|
6
|
+
require_relative "options"
|
|
6
7
|
require_relative "../analysis/runner"
|
|
7
8
|
require_relative "../cache/store"
|
|
8
9
|
require_relative "../triage"
|
|
@@ -42,7 +43,7 @@ module Rigor
|
|
|
42
43
|
options = { config: nil, format: "text", top: 10, sections: DEFAULT_SECTIONS }
|
|
43
44
|
OptionParser.new do |opts|
|
|
44
45
|
opts.banner = USAGE
|
|
45
|
-
|
|
46
|
+
Options.add_config(opts, options)
|
|
46
47
|
opts.on("--format=FORMAT", "Output format: text (default) or json") { |v| options[:format] = v }
|
|
47
48
|
opts.on("--top=N", Integer, "Hotspot-file count (default 10)") { |v| options[:top] = v }
|
|
48
49
|
opts.on("--hints-only", "Print only the heuristic-hints section") { options[:sections] = %i[hints] }
|
|
@@ -53,7 +53,7 @@ module Rigor
|
|
|
53
53
|
opts.banner = USAGE
|
|
54
54
|
opts.on("--format=FORMAT", "Output format: text or json") { |value| options[:format] = value }
|
|
55
55
|
opts.on("--trace", "Record fail-soft fallbacks via FallbackTracer") { options[:trace] = true }
|
|
56
|
-
|
|
56
|
+
Options.add_config(opts, options)
|
|
57
57
|
Options.add_editor_mode(opts, options)
|
|
58
58
|
end
|
|
59
59
|
parser.parse!(@argv)
|
|
@@ -4,6 +4,7 @@ require "optionparser"
|
|
|
4
4
|
require "prism"
|
|
5
5
|
|
|
6
6
|
require_relative "../configuration"
|
|
7
|
+
require_relative "options"
|
|
7
8
|
require_relative "../environment"
|
|
8
9
|
require_relative "../inference/coverage_scanner"
|
|
9
10
|
require_relative "../scope"
|
|
@@ -46,7 +47,7 @@ module Rigor
|
|
|
46
47
|
parser = OptionParser.new do |opts|
|
|
47
48
|
opts.banner = USAGE
|
|
48
49
|
opts.on("--format=FORMAT", "Output format: text or json") { |value| options[:format] = value }
|
|
49
|
-
|
|
50
|
+
Options.add_config(opts, options)
|
|
50
51
|
opts.on("--limit=N", Integer, "Max example events to print (text only)") do |value|
|
|
51
52
|
options[:limit] = value
|
|
52
53
|
end
|
data/lib/rigor/cli.rb
CHANGED
|
@@ -151,8 +151,9 @@ module Rigor
|
|
|
151
151
|
# - target_ruby: minimum Ruby version your project targets.
|
|
152
152
|
# - paths: directories scanned by `rigor check` and
|
|
153
153
|
# `rigor type-scan` when no path is given.
|
|
154
|
-
# - plugins:
|
|
155
|
-
#
|
|
154
|
+
# - plugins: opt-in list of plugin gem names to load.
|
|
155
|
+
# See https://github.com/rigortype/rigor/tree/main/plugins
|
|
156
|
+
# for production plugins (rigor-activerecord, rigor-sorbet, …).
|
|
156
157
|
# - disable: list of `rigor check` rule identifiers to
|
|
157
158
|
# silence project-wide. The shipped rules are
|
|
158
159
|
# call.undefined-method, call.wrong-arity,
|
|
@@ -9,10 +9,8 @@ module Rigor
|
|
|
9
9
|
# inference instead of degrading to `Dynamic[top]` at the
|
|
10
10
|
# dependency boundary.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# wires `Analysis::DependencySourceInference` against this
|
|
15
|
-
# value object.
|
|
12
|
+
# Parser for the `dependencies:` YAML section; consumed by
|
|
13
|
+
# `Analysis::DependencySourceInference` (ADR-10).
|
|
16
14
|
class Dependencies
|
|
17
15
|
# Walking modes per
|
|
18
16
|
# [ADR-10 § "Decision"](../../../docs/adr/10-dependency-source-inference.md#decision).
|
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
|
|
@@ -593,7 +621,7 @@ module Rigor
|
|
|
593
621
|
case value
|
|
594
622
|
when nil, false then { "mode" => "none" }
|
|
595
623
|
when true then { "mode" => "all" }
|
|
596
|
-
when Array then { "mode" => "list", "ids" => value
|
|
624
|
+
when Array then { "mode" => "list", "ids" => freeze_ids(value) }
|
|
597
625
|
when Hash then coerce_bleeding_edge_hash(value)
|
|
598
626
|
else
|
|
599
627
|
raise ArgumentError,
|
|
@@ -605,12 +633,22 @@ module Rigor
|
|
|
605
633
|
def coerce_bleeding_edge_hash(value)
|
|
606
634
|
hash = value.to_h { |k, v| [k.to_s, v] }
|
|
607
635
|
if hash.fetch("all", false) == true
|
|
608
|
-
{ "mode" => "all", "except" => Array(hash["except"])
|
|
636
|
+
{ "mode" => "all", "except" => freeze_ids(Array(hash["except"])) }
|
|
609
637
|
else
|
|
610
638
|
{ "mode" => "none" }
|
|
611
639
|
end
|
|
612
640
|
end
|
|
613
641
|
|
|
642
|
+
# Feature ids reach `coerce_bleeding_edge` from YAML, a `to_h` round-trip,
|
|
643
|
+
# or the CLI's `--bleeding-edge=a,b` (all runtime-created, non-frozen
|
|
644
|
+
# Strings). Each id String is frozen so the normalized selector — and thus
|
|
645
|
+
# the whole Configuration — stays deeply frozen and `Ractor.shareable?`
|
|
646
|
+
# for the worker path (the same invariant `#initialize`'s final `freeze`
|
|
647
|
+
# upholds for every other field).
|
|
648
|
+
def freeze_ids(ids)
|
|
649
|
+
ids.map { |id| id.to_s.dup.freeze }.freeze
|
|
650
|
+
end
|
|
651
|
+
|
|
614
652
|
# Renders the normalized selector back into the user-facing
|
|
615
653
|
# `bleeding_edge:` form for `#to_h` round-trips.
|
|
616
654
|
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}]
|
|
@@ -363,11 +363,11 @@ module Rigor
|
|
|
363
363
|
# out at build time so the loader stays robust to fixtures and
|
|
364
364
|
# bare repositories.
|
|
365
365
|
# @param cache_store [Rigor::Cache::Store, nil] the persistent
|
|
366
|
-
# cache the loader
|
|
367
|
-
#
|
|
368
|
-
#
|
|
369
|
-
#
|
|
370
|
-
# through here when
|
|
366
|
+
# cache the loader threads through to `RbsEnvironment`,
|
|
367
|
+
# `RbsKnownClassNames`, `RbsConstantTable`,
|
|
368
|
+
# `RbsClassAncestorTable`, and `RbsClassTypeParamNames`
|
|
369
|
+
# producers. Pass `nil` (the default) to skip caching; the
|
|
370
|
+
# runner threads its own Store through here when enabled.
|
|
371
371
|
# @param virtual_rbs [Array<[String, String]>] ADR-32 WD4 —
|
|
372
372
|
# `[virtual_filename, rbs_source]` pairs synthesised from
|
|
373
373
|
# project source by a plugin's
|
|
@@ -643,6 +643,29 @@ module Rigor
|
|
|
643
643
|
interface_definition(interface_name)&.methods&.keys
|
|
644
644
|
end
|
|
645
645
|
|
|
646
|
+
# @param rbs_alias [RBS::Types::Alias] a type-alias reference (`string`,
|
|
647
|
+
# `int`, `range[int?]`, …) appearing in a method signature.
|
|
648
|
+
# @return [RBS::Types::t, nil] the alias's aliased type one level out,
|
|
649
|
+
# with type arguments substituted for a generic alias (`string` →
|
|
650
|
+
# `::String | ::_ToStr`; `range[int?]` → `::Range[int?] |
|
|
651
|
+
# ::_Range[int?]`), or nil for an unresolved name. Lets a caller see
|
|
652
|
+
# through the alias that {Inference::RbsTypeTranslator} otherwise
|
|
653
|
+
# degrades to `untyped`, which is why an interface/alias parameter
|
|
654
|
+
# does not reject `nil`. `expand_alias2` handles the (rarer) generic
|
|
655
|
+
# case — a `range[T]` param previously fell back to "admits", which
|
|
656
|
+
# suppressed e.g. `MatchData#[](nil)`.
|
|
657
|
+
def expand_type_alias(rbs_alias)
|
|
658
|
+
return nil if env.nil?
|
|
659
|
+
|
|
660
|
+
name = rbs_alias.name
|
|
661
|
+
name = name.absolute! unless name.absolute?
|
|
662
|
+
return nil unless env.type_alias_decls.key?(name)
|
|
663
|
+
|
|
664
|
+
builder.expand_alias2(name, rbs_alias.args)
|
|
665
|
+
rescue ::RBS::BaseError, StandardError
|
|
666
|
+
nil
|
|
667
|
+
end
|
|
668
|
+
|
|
646
669
|
# @return [RBS::Definition, nil] the resolved singleton (class
|
|
647
670
|
# object) definition for `class_name`. The methods on this
|
|
648
671
|
# definition are the *class methods* of `class_name`, including
|
|
@@ -1034,6 +1057,8 @@ module Rigor
|
|
|
1034
1057
|
rbs_name = parse_type_name(class_name)
|
|
1035
1058
|
return nil unless rbs_name
|
|
1036
1059
|
return nil if env.nil?
|
|
1060
|
+
|
|
1061
|
+
rbs_name = canonical_module_name(rbs_name)
|
|
1037
1062
|
return nil unless env.class_decls.key?(rbs_name)
|
|
1038
1063
|
|
|
1039
1064
|
builder.build_instance(rbs_name)
|
|
@@ -1045,6 +1070,8 @@ module Rigor
|
|
|
1045
1070
|
rbs_name = parse_type_name(class_name)
|
|
1046
1071
|
return nil unless rbs_name
|
|
1047
1072
|
return nil if env.nil?
|
|
1073
|
+
|
|
1074
|
+
rbs_name = canonical_module_name(rbs_name)
|
|
1048
1075
|
return nil unless env.class_decls.key?(rbs_name)
|
|
1049
1076
|
|
|
1050
1077
|
builder.build_singleton(rbs_name)
|
|
@@ -1052,6 +1079,23 @@ module Rigor
|
|
|
1052
1079
|
nil
|
|
1053
1080
|
end
|
|
1054
1081
|
|
|
1082
|
+
# Resolve an RBS class/module ALIAS to its canonical declared name.
|
|
1083
|
+
# `class Mutex = Thread::Mutex` lives only in `class_alias_decls`, so
|
|
1084
|
+
# `class_known?` reports it (it checks that table) but the definition
|
|
1085
|
+
# builder — which only knows `class_decls` — could not enumerate its
|
|
1086
|
+
# methods, leaving alias classes (`Mutex`, and any `X = Y`) with no
|
|
1087
|
+
# resolvable method surface. Normalising via the env (RBS's own alias
|
|
1088
|
+
# resolution) before the `class_decls` guard fixes dispatch AND the
|
|
1089
|
+
# `call.undefined-method` existence check on them. A non-alias name, or
|
|
1090
|
+
# one that does not normalise, is returned unchanged.
|
|
1091
|
+
def canonical_module_name(rbs_name)
|
|
1092
|
+
return rbs_name unless env.class_alias_decls.key?(rbs_name)
|
|
1093
|
+
|
|
1094
|
+
env.normalize_module_name?(rbs_name) || rbs_name
|
|
1095
|
+
rescue ::RBS::BaseError
|
|
1096
|
+
rbs_name
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1055
1099
|
# Memoised on `@state` (the per-loader store also holding `:env` /
|
|
1056
1100
|
# `:builder`): `RBS::TypeName.parse` is a pure, deterministic
|
|
1057
1101
|
# 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.
|
|
@@ -123,6 +125,7 @@ module Rigor
|
|
|
123
125
|
# build at all.
|
|
124
126
|
@hkt_registry_base = hkt_registry || Inference::HktRegistry::EMPTY
|
|
125
127
|
@hkt_registry_holder = HktRegistryHolder.new
|
|
128
|
+
@constant_type_cache = ConstantTypeCacheHolder.new
|
|
126
129
|
@name_scope = build_name_scope
|
|
127
130
|
freeze
|
|
128
131
|
end
|
|
@@ -453,11 +456,11 @@ module Rigor
|
|
|
453
456
|
def invoke_synthesizer_safely(callable, path)
|
|
454
457
|
callable.call(path.to_s)
|
|
455
458
|
rescue StandardError
|
|
456
|
-
# WD6 fail-soft — a synthesizer that raises does NOT
|
|
457
|
-
#
|
|
458
|
-
# `source-rbs-synthesis-failed`
|
|
459
|
-
#
|
|
460
|
-
#
|
|
459
|
+
# WD6 fail-soft — a synthesizer that raises does NOT crash
|
|
460
|
+
# analysis. Unlike the `[:error, msg]` return path (which
|
|
461
|
+
# the runner surfaces as `source-rbs-synthesis-failed`),
|
|
462
|
+
# an unhandled raise is swallowed silently; the
|
|
463
|
+
# unexamined-raise channel is deliberately silent per WD6.
|
|
461
464
|
nil
|
|
462
465
|
end
|
|
463
466
|
end
|
|
@@ -504,7 +507,14 @@ module Rigor
|
|
|
504
507
|
def constant_for_name(name)
|
|
505
508
|
return nil if rbs_loader.nil?
|
|
506
509
|
|
|
507
|
-
|
|
510
|
+
# Pure function of `name` for this Environment's lifetime — the
|
|
511
|
+
# refinement table and the RBS constant table are both fixed — so
|
|
512
|
+
# memoise across the lexical-candidate ladder's heavy name reuse.
|
|
513
|
+
key = name.to_s
|
|
514
|
+
@constant_type_cache.fetch(key) do
|
|
515
|
+
Builtins::PredefinedConstantRefinements.lookup(key) ||
|
|
516
|
+
rbs_loader.constant_type(key)
|
|
517
|
+
end
|
|
508
518
|
end
|
|
509
519
|
|
|
510
520
|
# 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`
|
|
@@ -249,6 +249,12 @@ module Rigor
|
|
|
249
249
|
# anonymous local-bound form projects to `Data` itself).
|
|
250
250
|
accepts(self_type, project_data_instance_to_nominal(other_type), mode: mode)
|
|
251
251
|
.with_reason("projected DataInstance to Nominal[#{other_type.class_name || 'Data'}]")
|
|
252
|
+
when Type::StructInstance
|
|
253
|
+
# ADR-48 Struct follow-up: same projection as DataInstance — a
|
|
254
|
+
# class-tagged Struct value is exactly one value of its tagging
|
|
255
|
+
# class (the anonymous form projects to `Struct` itself).
|
|
256
|
+
accepts(self_type, project_struct_instance_to_nominal(other_type), mode: mode)
|
|
257
|
+
.with_reason("projected StructInstance to Nominal[#{other_type.class_name || 'Struct'}]")
|
|
252
258
|
when Type::Difference, Type::Refined
|
|
253
259
|
# A refinement carrier's value set is a subset of its
|
|
254
260
|
# base. So if `self` (Nominal) accepts the base, it
|
|
@@ -386,6 +392,10 @@ module Rigor
|
|
|
386
392
|
Type::Combinator.nominal_of(instance.class_name || "Data")
|
|
387
393
|
end
|
|
388
394
|
|
|
395
|
+
def project_struct_instance_to_nominal(instance)
|
|
396
|
+
Type::Combinator.nominal_of(instance.class_name || "Struct")
|
|
397
|
+
end
|
|
398
|
+
|
|
389
399
|
def project_hash_shape_to_nominal(shape)
|
|
390
400
|
return Type::Combinator.nominal_of(Hash) if shape.pairs.empty?
|
|
391
401
|
|
|
@@ -822,15 +832,13 @@ module Rigor
|
|
|
822
832
|
Type::AcceptsResult.no(mode: mode, reasons: reason)
|
|
823
833
|
end
|
|
824
834
|
|
|
825
|
-
#
|
|
826
|
-
# "is D a subclass of C?"
|
|
827
|
-
#
|
|
828
|
-
#
|
|
829
|
-
#
|
|
830
|
-
#
|
|
831
|
-
#
|
|
832
|
-
# so ahead-of-time type checking no longer relies on Ruby
|
|
833
|
-
# loading the application classes.
|
|
835
|
+
# Uses Ruby's actual class hierarchy via Object.const_get to answer
|
|
836
|
+
# "is D a subclass of C?" for core, stdlib, and application classes.
|
|
837
|
+
# When either name fails to resolve we surface "maybe": the caller
|
|
838
|
+
# (overload selector) treats yes/maybe identically, so the conservative
|
|
839
|
+
# answer keeps overload coverage intact. RbsHierarchy exists but this
|
|
840
|
+
# path does not yet consult it; migration to an RBS-driven lookup
|
|
841
|
+
# is deferred.
|
|
834
842
|
def class_subtype_result(target_name:, actual_name:, mode:, kind:)
|
|
835
843
|
return Type::AcceptsResult.yes(mode: mode, reasons: "exact name match") if target_name == actual_name
|
|
836
844
|
|
|
@@ -183,9 +183,8 @@ module Rigor
|
|
|
183
183
|
# `|*rest|` binds an Array of the leftover positional arguments.
|
|
184
184
|
# The expected-types array is per-position, not per-rest; we
|
|
185
185
|
# cannot reliably pick a single element type for rest, so we
|
|
186
|
-
# default to `Array[Dynamic[Top]]`.
|
|
187
|
-
#
|
|
188
|
-
# available.
|
|
186
|
+
# default to `Array[Dynamic[Top]]`. Element-type precision for
|
|
187
|
+
# rest parameters is deferred (demand-gated).
|
|
189
188
|
def bind_rest(params_node, bindings)
|
|
190
189
|
rest = params_node.rest
|
|
191
190
|
return unless rest.respond_to?(:name) && rest&.name
|
|
@@ -11,8 +11,8 @@ module Rigor
|
|
|
11
11
|
# catalog is NOT routed through
|
|
12
12
|
# `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS`
|
|
13
13
|
# (which dispatches on the receiver's concrete class).
|
|
14
|
-
# The data is
|
|
15
|
-
#
|
|
14
|
+
# The data is wired into `MODULE_CATALOGS` in
|
|
15
|
+
# `MethodDispatcher::ConstantFolding` (ancestor-chain lookup).
|
|
16
16
|
COMPARABLE_CATALOG = MethodCatalog.for_topic(
|
|
17
17
|
"comparable",
|
|
18
18
|
mutating_selectors: {
|