rigortype 0.1.18 → 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/README.md +159 -224
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +32 -23
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +151 -23
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules.rb +756 -132
- 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/diagnostic.rb +8 -0
- 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 +19 -18
- data/lib/rigor/analysis/runner/project_pre_passes.rb +13 -9
- data/lib/rigor/analysis/runner.rb +75 -27
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +31 -25
- data/lib/rigor/bleeding_edge.rb +123 -0
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/descriptor.rb +86 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +122 -16
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +118 -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 +4 -5
- data/lib/rigor/cli/plugins_renderer.rb +0 -2
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/show_bleedingedge_command.rb +114 -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 +8 -4
- data/lib/rigor/cli/triage_renderer.rb +15 -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 +12 -3
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration/severity_profile.rb +13 -1
- data/lib/rigor/configuration.rb +100 -6
- 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 +74 -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/body_fixpoint.rb +89 -0
- data/lib/rigor/inference/budget_trace.rb +29 -2
- 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 +1072 -71
- 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/macro_block_self_type.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +210 -35
- 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/reduce_folding.rb +281 -0
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +237 -24
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +112 -49
- data/lib/rigor/inference/method_parameter_binder.rb +56 -2
- data/lib/rigor/inference/multi_target_binder.rb +46 -3
- data/lib/rigor/inference/mutation_widening.rb +147 -11
- data/lib/rigor/inference/narrowing.rb +284 -53
- 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 +821 -76
- data/lib/rigor/inference/statement_evaluator.rb +1179 -102
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
- 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 +245 -87
- data/lib/rigor/plugin/macro/block_as_method.rb +25 -25
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +6 -8
- data/lib/rigor/plugin/manifest.rb +49 -90
- data/lib/rigor/plugin/node_rule_walk.rb +59 -14
- data/lib/rigor/plugin/registry.rb +18 -18
- 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 +16 -2
- data/lib/rigor/scope.rb +185 -16
- data/lib/rigor/sig_gen/generator.rb +8 -0
- 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/triage/catalogue.rb +4 -19
- data/lib/rigor/triage.rb +69 -1
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +45 -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 +16 -32
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +34 -100
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +26 -27
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +9 -8
- 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 +18 -49
- 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 +4 -4
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
- 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 +22 -35
- 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 +16 -23
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
- 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 +21 -27
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
- 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 +52 -40
- data/sig/rigor/analysis/fact_store.rbs +3 -0
- data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
- data/sig/rigor/plugin/base.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +1 -2
- data/sig/rigor/scope.rbs +18 -1
- data/sig/rigor/type.rbs +37 -1
- data/sig/rigor.rbs +1 -1
- data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
- data/skills/rigor-plugin-author/SKILL.md +6 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
- metadata +25 -2
- data/lib/rigor/plugin/macro/external_file.rb +0 -143
data/lib/rigor/configuration.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
|
|
5
|
+
require_relative "bleeding_edge"
|
|
5
6
|
require_relative "configuration/dependencies"
|
|
6
7
|
require_relative "configuration/severity_profile"
|
|
7
8
|
|
|
@@ -87,6 +88,15 @@ module Rigor
|
|
|
87
88
|
},
|
|
88
89
|
"severity_profile" => "balanced",
|
|
89
90
|
"severity_overrides" => {},
|
|
91
|
+
# ADR-50 § WD2 — bleeding-edge overlay opt-in. Selects which of
|
|
92
|
+
# the *next major's* queued changes ({Rigor::BleedingEdge}) this
|
|
93
|
+
# project adopts early. Orthogonal to `severity_profile:`. Accepts
|
|
94
|
+
# `false` (default — adopt none), `true` (adopt the whole
|
|
95
|
+
# overlay), a list of feature ids (adopt only those), or
|
|
96
|
+
# `{ all: true, except: [ids] }` (adopt all but the named). The
|
|
97
|
+
# overlay is empty today, so every form is currently a no-op; it
|
|
98
|
+
# becomes live when the first discipline is queued for a major.
|
|
99
|
+
"bleeding_edge" => false,
|
|
90
100
|
"dependencies" => {
|
|
91
101
|
"source_inference" => [],
|
|
92
102
|
"budget_per_gem" => Configuration::Dependencies::DEFAULT_BUDGET_PER_GEM
|
|
@@ -181,6 +191,7 @@ module Rigor
|
|
|
181
191
|
:plugins_io_network, :plugins_io_allowed_paths,
|
|
182
192
|
:plugins_io_allowed_url_hosts,
|
|
183
193
|
:severity_profile, :severity_overrides,
|
|
194
|
+
:bleeding_edge, :bleeding_edge_severity_overrides,
|
|
184
195
|
:dependencies, :parallel_workers,
|
|
185
196
|
:bundler_bundle_path, :bundler_auto_detect, :bundler_lockfile,
|
|
186
197
|
:rbs_collection_lockfile, :rbs_collection_auto_detect,
|
|
@@ -222,6 +233,9 @@ module Rigor
|
|
|
222
233
|
# included files first (in declaration order), then the
|
|
223
234
|
# current file's own keys override. Relative paths inside
|
|
224
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}).
|
|
225
239
|
def self.load_with_includes(path, visited: Set.new)
|
|
226
240
|
absolute = File.expand_path(path)
|
|
227
241
|
raise ArgumentError, "circular include: #{absolute}" if visited.include?(absolute)
|
|
@@ -317,7 +331,7 @@ module Rigor
|
|
|
317
331
|
out["source_inference"] = left_si + right_si unless both_empty # rigor:disable flow.always-truthy-condition
|
|
318
332
|
out
|
|
319
333
|
end
|
|
320
|
-
private_class_method :
|
|
334
|
+
private_class_method :merge_includes, :resolve_paths_in, :deep_merge,
|
|
321
335
|
:merge_value, :merge_dependencies_hash
|
|
322
336
|
|
|
323
337
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
@@ -355,6 +369,10 @@ module Rigor
|
|
|
355
369
|
@severity_overrides = coerce_severity_overrides(
|
|
356
370
|
data.fetch("severity_overrides", DEFAULTS.fetch("severity_overrides"))
|
|
357
371
|
)
|
|
372
|
+
@bleeding_edge = coerce_bleeding_edge(
|
|
373
|
+
data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge"))
|
|
374
|
+
)
|
|
375
|
+
@bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
|
|
358
376
|
@dependencies = Dependencies.from_h(
|
|
359
377
|
data.fetch("dependencies", DEFAULTS.fetch("dependencies"))
|
|
360
378
|
)
|
|
@@ -383,7 +401,7 @@ module Rigor
|
|
|
383
401
|
end
|
|
384
402
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
385
403
|
|
|
386
|
-
def to_h # rubocop:disable Metrics/MethodLength
|
|
404
|
+
def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
387
405
|
{
|
|
388
406
|
"target_ruby" => target_ruby,
|
|
389
407
|
"paths" => paths,
|
|
@@ -405,6 +423,7 @@ module Rigor
|
|
|
405
423
|
},
|
|
406
424
|
"severity_profile" => severity_profile.to_s,
|
|
407
425
|
"severity_overrides" => severity_overrides.to_h { |k, v| [k, v.to_s] },
|
|
426
|
+
"bleeding_edge" => bleeding_edge_to_h,
|
|
408
427
|
"dependencies" => dependencies.to_h,
|
|
409
428
|
"parallel" => {
|
|
410
429
|
"workers" => parallel_workers
|
|
@@ -421,6 +440,32 @@ module Rigor
|
|
|
421
440
|
}
|
|
422
441
|
end
|
|
423
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
|
+
|
|
424
469
|
private
|
|
425
470
|
|
|
426
471
|
# ADR-17 slice 4 — `pre_eval:` glob expansion. Each entry is
|
|
@@ -483,10 +528,9 @@ module Rigor
|
|
|
483
528
|
s.dup.freeze
|
|
484
529
|
end
|
|
485
530
|
|
|
486
|
-
#
|
|
487
|
-
#
|
|
488
|
-
#
|
|
489
|
-
# 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.
|
|
490
534
|
#
|
|
491
535
|
# The accepted set is duplicated from
|
|
492
536
|
# {Rigor::Plugin::TrustPolicy::VALID_NETWORK_POLICIES} so
|
|
@@ -566,5 +610,55 @@ module Rigor
|
|
|
566
610
|
[k.to_s, sym]
|
|
567
611
|
end.freeze
|
|
568
612
|
end
|
|
613
|
+
|
|
614
|
+
# ADR-50 § WD2 — normalizes the `bleeding_edge:` selector to a
|
|
615
|
+
# canonical `{ "mode" => … }` hash (interpreted by
|
|
616
|
+
# {Rigor::BleedingEdge}). Validates *shape* only; membership against
|
|
617
|
+
# the overlay is intentionally NOT checked here (an unknown id stays
|
|
618
|
+
# inert, like an unknown `severity_overrides:` rule). Deep-frozen so
|
|
619
|
+
# the Configuration stays `Ractor.shareable?`.
|
|
620
|
+
def coerce_bleeding_edge(value)
|
|
621
|
+
case value
|
|
622
|
+
when nil, false then { "mode" => "none" }
|
|
623
|
+
when true then { "mode" => "all" }
|
|
624
|
+
when Array then { "mode" => "list", "ids" => freeze_ids(value) }
|
|
625
|
+
when Hash then coerce_bleeding_edge_hash(value)
|
|
626
|
+
else
|
|
627
|
+
raise ArgumentError,
|
|
628
|
+
"bleeding_edge must be true, false, a list of feature ids, " \
|
|
629
|
+
"or { all: true, except: [...] }, got #{value.inspect}"
|
|
630
|
+
end.freeze
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def coerce_bleeding_edge_hash(value)
|
|
634
|
+
hash = value.to_h { |k, v| [k.to_s, v] }
|
|
635
|
+
if hash.fetch("all", false) == true
|
|
636
|
+
{ "mode" => "all", "except" => freeze_ids(Array(hash["except"])) }
|
|
637
|
+
else
|
|
638
|
+
{ "mode" => "none" }
|
|
639
|
+
end
|
|
640
|
+
end
|
|
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
|
+
|
|
652
|
+
# Renders the normalized selector back into the user-facing
|
|
653
|
+
# `bleeding_edge:` form for `#to_h` round-trips.
|
|
654
|
+
def bleeding_edge_to_h
|
|
655
|
+
case bleeding_edge["mode"]
|
|
656
|
+
when "all"
|
|
657
|
+
except = bleeding_edge["except"] || []
|
|
658
|
+
except.empty? || { "all" => true, "except" => except }
|
|
659
|
+
when "list" then bleeding_edge["ids"] || []
|
|
660
|
+
else false
|
|
661
|
+
end
|
|
662
|
+
end
|
|
569
663
|
end
|
|
570
664
|
end
|
|
@@ -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}]
|
|
@@ -88,6 +88,15 @@ module Rigor
|
|
|
88
88
|
vendored_gem_sig_paths.each do |path|
|
|
89
89
|
rbs_loader.add(path: path) if path.directory?
|
|
90
90
|
end
|
|
91
|
+
# Rigor-owned core overlay — loaded LAST so an upstream
|
|
92
|
+
# declaration always wins on conflict; these reopenings only
|
|
93
|
+
# fill genuine holes (e.g. `Numeric#to_f`/`to_i`/`to_r`, which
|
|
94
|
+
# upstream RBS declares on the concrete subclasses but not on
|
|
95
|
+
# the abstract `Numeric` that Rigor's arithmetic-chain widening
|
|
96
|
+
# produces).
|
|
97
|
+
core_overlay_sig_paths.each do |path|
|
|
98
|
+
rbs_loader.add(path: path) if path.directory?
|
|
99
|
+
end
|
|
91
100
|
env = RBS::Environment.from_loader(rbs_loader)
|
|
92
101
|
add_virtual_rbs(env, virtual_rbs)
|
|
93
102
|
synthesize_missing_namespaces(env)
|
|
@@ -298,6 +307,22 @@ module Rigor
|
|
|
298
307
|
).freeze
|
|
299
308
|
private_constant :VENDORED_GEM_SIGS_ROOT
|
|
300
309
|
|
|
310
|
+
# Rigor-owned core-overlay RBS (`data/core_overlay/`). Reopens
|
|
311
|
+
# Ruby core classes to add methods upstream `ruby/rbs` omits but
|
|
312
|
+
# which every concrete value answers at runtime — loaded last so
|
|
313
|
+
# upstream always wins on conflict. Public so the cache descriptor
|
|
314
|
+
# can digest these files into the env-blob key.
|
|
315
|
+
CORE_OVERLAY_SIGS_ROOT = File.expand_path(
|
|
316
|
+
"../../../data/core_overlay",
|
|
317
|
+
__dir__
|
|
318
|
+
).freeze
|
|
319
|
+
|
|
320
|
+
def core_overlay_sig_paths
|
|
321
|
+
return [] unless File.directory?(CORE_OVERLAY_SIGS_ROOT)
|
|
322
|
+
|
|
323
|
+
[Pathname(CORE_OVERLAY_SIGS_ROOT)]
|
|
324
|
+
end
|
|
325
|
+
|
|
301
326
|
def vendored_gem_sig_paths
|
|
302
327
|
return [] unless File.directory?(VENDORED_GEM_SIGS_ROOT)
|
|
303
328
|
|
|
@@ -338,11 +363,11 @@ module Rigor
|
|
|
338
363
|
# out at build time so the loader stays robust to fixtures and
|
|
339
364
|
# bare repositories.
|
|
340
365
|
# @param cache_store [Rigor::Cache::Store, nil] the persistent
|
|
341
|
-
# cache the loader
|
|
342
|
-
#
|
|
343
|
-
#
|
|
344
|
-
#
|
|
345
|
-
# 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.
|
|
346
371
|
# @param virtual_rbs [Array<[String, String]>] ADR-32 WD4 —
|
|
347
372
|
# `[virtual_filename, rbs_source]` pairs synthesised from
|
|
348
373
|
# project source by a plugin's
|
|
@@ -618,6 +643,29 @@ module Rigor
|
|
|
618
643
|
interface_definition(interface_name)&.methods&.keys
|
|
619
644
|
end
|
|
620
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
|
+
|
|
621
669
|
# @return [RBS::Definition, nil] the resolved singleton (class
|
|
622
670
|
# object) definition for `class_name`. The methods on this
|
|
623
671
|
# definition are the *class methods* of `class_name`, including
|
|
@@ -1009,6 +1057,8 @@ module Rigor
|
|
|
1009
1057
|
rbs_name = parse_type_name(class_name)
|
|
1010
1058
|
return nil unless rbs_name
|
|
1011
1059
|
return nil if env.nil?
|
|
1060
|
+
|
|
1061
|
+
rbs_name = canonical_module_name(rbs_name)
|
|
1012
1062
|
return nil unless env.class_decls.key?(rbs_name)
|
|
1013
1063
|
|
|
1014
1064
|
builder.build_instance(rbs_name)
|
|
@@ -1020,6 +1070,8 @@ module Rigor
|
|
|
1020
1070
|
rbs_name = parse_type_name(class_name)
|
|
1021
1071
|
return nil unless rbs_name
|
|
1022
1072
|
return nil if env.nil?
|
|
1073
|
+
|
|
1074
|
+
rbs_name = canonical_module_name(rbs_name)
|
|
1023
1075
|
return nil unless env.class_decls.key?(rbs_name)
|
|
1024
1076
|
|
|
1025
1077
|
builder.build_singleton(rbs_name)
|
|
@@ -1027,6 +1079,23 @@ module Rigor
|
|
|
1027
1079
|
nil
|
|
1028
1080
|
end
|
|
1029
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
|
+
|
|
1030
1099
|
# Memoised on `@state` (the per-loader store also holding `:env` /
|
|
1031
1100
|
# `:builder`): `RBS::TypeName.parse` is a pure, deterministic
|
|
1032
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`
|