rigortype 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +18 -5
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/02-cli-reference.md +19 -1
- data/docs/manual/03-configuration.md +7 -5
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +41 -4
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +11 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +292 -59
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
- data/lib/rigor/analysis/incremental_session.rb +103 -23
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +241 -37
- data/lib/rigor/analysis/reachability/scan.rb +490 -77
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/run_cache_probe.rb +1 -1
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +62 -13
- data/lib/rigor/analysis/worker_session.rb +8 -5
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +55 -5
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +140 -40
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +6 -3
- data/lib/rigor/cli/unused_command.rb +166 -27
- data/lib/rigor/cli.rb +39 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +61 -2
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +316 -14
- data/lib/rigor/environment/required_features.rb +208 -0
- data/lib/rigor/environment.rb +33 -8
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +43 -3
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +261 -29
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +286 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +96 -14
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +12 -7
- data/lib/rigor/inference/project_patched_scanner.rb +108 -26
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
- data/lib/rigor/inference/refined_dispatch.rb +320 -0
- data/lib/rigor/inference/repeated_or_writes.rb +2 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +827 -65
- data/lib/rigor/inference/statement_evaluator.rb +294 -51
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +87 -5
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +118 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +433 -0
- data/lib/rigor/lens/members.rb +153 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +280 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +40 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +48 -6
- data/lib/rigor/protection/mutation_cache.rb +3 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +211 -21
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +100 -10
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +30 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +9 -4
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +83 -1
data/lib/rigor/environment.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
5
|
require_relative "environment/default_libraries"
|
|
6
|
+
require_relative "environment/required_features"
|
|
6
7
|
require_relative "environment/class_registry"
|
|
7
8
|
require_relative "environment/rbs_loader"
|
|
8
9
|
require_relative "environment/reflection"
|
|
@@ -96,7 +97,7 @@ module Rigor
|
|
|
96
97
|
# for the overlay, instead of aborting the run outright (see {#hkt_registry}).
|
|
97
98
|
@hkt_scan_failure = FailureSlot.new
|
|
98
99
|
@constant_type_cache = ConstantTypeCacheHolder.new
|
|
99
|
-
# ADR-82 WD9 — `[gem_name, version]`
|
|
100
|
+
# ADR-82 WD9 — `[gem_name, version, variants]` entries for the locked gems with no resolvable RBS. The
|
|
100
101
|
# root-constant ownership index over them is built lazily (first unresolved constant read) so runs
|
|
101
102
|
# whose constants all resolve never pay the entry-file scan.
|
|
102
103
|
@missing_rbs_gems = missing_rbs_gems.freeze
|
|
@@ -275,7 +276,7 @@ module Rigor
|
|
|
275
276
|
bundler_lockfile: nil,
|
|
276
277
|
rbs_collection_lockfile: nil, rbs_collection_auto_detect: false,
|
|
277
278
|
synthetic_method_index: nil, project_patched_methods: nil,
|
|
278
|
-
source_files: [], locked_gems: nil)
|
|
279
|
+
source_files: [], locked_gems: nil, required_features: nil)
|
|
279
280
|
resolved_paths = project_signature_roots(signature_paths, root: root)
|
|
280
281
|
# O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
|
|
281
282
|
# root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
|
|
@@ -314,8 +315,10 @@ module Rigor
|
|
|
314
315
|
# and the `data/vendored_gem_sigs/` bundle (`redis`, `nokogiri`, `pg`, …). `skip_gem_names:` passes
|
|
315
316
|
# both sets so the collection copy doesn't double-declare against rigor's bundled RBS (the
|
|
316
317
|
# `RBS::DuplicatedDeclarationError` hazard).
|
|
317
|
-
|
|
318
|
-
|
|
318
|
+
# Issue #1700 — plus the gated vendored directories this run's source activates ({#feature_tokens}).
|
|
319
|
+
merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s) +
|
|
320
|
+
feature_tokens(required_features, source_files, libraries)).uniq
|
|
321
|
+
skip_gem_names = collection_skip_gem_names(merged_libraries)
|
|
319
322
|
collection_paths = RbsCollectionDiscovery.discover(
|
|
320
323
|
lockfile_path: rbs_collection_lockfile,
|
|
321
324
|
project_root: root,
|
|
@@ -400,6 +403,21 @@ module Rigor
|
|
|
400
403
|
|
|
401
404
|
private
|
|
402
405
|
|
|
406
|
+
# Issue #1700 — the `RequiredFeatures` library tokens for the gated vendored directories this run asks for:
|
|
407
|
+
# each feature its source requires and each one the configuration's `libraries:` names. `required_features`
|
|
408
|
+
# is the scan a caller already took; nil scans `source_files`. Whether a directory then loads is decided
|
|
409
|
+
# by `RbsLoader.build_env_for` against the environment it builds.
|
|
410
|
+
def feature_tokens(required_features, source_files, libraries)
|
|
411
|
+
RequiredFeatures.tokens(required_features || RequiredFeatures.scan(source_files), libraries)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# The gem names an `rbs collection` copy must not double-declare against: the library list and every
|
|
415
|
+
# vendored directory except a `RequiredFeatures`-gated one. A gated directory stands down for any other
|
|
416
|
+
# copy of its declarations (`GatedSignatureGuard`), so the collection's copy is kept and wins.
|
|
417
|
+
def collection_skip_gem_names(library_names)
|
|
418
|
+
library_names + RbsLoader.vendored_gem_names.reject { |name| RequiredFeatures.gated_dir?(name) }
|
|
419
|
+
end
|
|
420
|
+
|
|
403
421
|
def default_signature_paths(root)
|
|
404
422
|
sig = Pathname(root) / DEFAULT_PROJECT_SIG_DIR
|
|
405
423
|
sig.directory? ? [sig] : []
|
|
@@ -426,8 +444,8 @@ module Rigor
|
|
|
426
444
|
end
|
|
427
445
|
|
|
428
446
|
# The `:missing` classification (locked gems with no RBS through any resolution path), computed once
|
|
429
|
-
# and consumed twice: as `[gem_name, version]`
|
|
430
|
-
# as the eligible-gem set for the ADR-72 overlay resolution. Returns `[
|
|
447
|
+
# and consumed twice: as `[gem_name, version, variants]` entries for the ADR-82 WD9 constant-ownership
|
|
448
|
+
# index, and as the eligible-gem set for the ADR-72 overlay resolution. Returns `[entries, overlay_paths]`.
|
|
431
449
|
def missing_gems_and_overlay_paths(locked:, default_libraries:, bundle_sig_paths:,
|
|
432
450
|
rbs_collection_paths:, plugin_registry:, signature_paths: [],
|
|
433
451
|
bundle_root: nil)
|
|
@@ -440,7 +458,7 @@ module Rigor
|
|
|
440
458
|
missing_gem_names: rows.map(&:gem_name), plugin_registry: plugin_registry,
|
|
441
459
|
signature_paths: signature_paths
|
|
442
460
|
)
|
|
443
|
-
[rows
|
|
461
|
+
[index_entries(rows, locked), overlays]
|
|
444
462
|
end
|
|
445
463
|
|
|
446
464
|
# Issue #530 — a project with no `Gemfile.lock` still has gems on disk, and before this it handed the
|
|
@@ -454,7 +472,14 @@ module Rigor
|
|
|
454
472
|
return [[], []] if installed.empty?
|
|
455
473
|
|
|
456
474
|
rows = missing_rows(installed, default_libraries, bundle_sig_paths, rbs_collection_paths)
|
|
457
|
-
[rows
|
|
475
|
+
[index_entries(rows, installed), []]
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# The constant-ownership index's input: each `:missing` row with every `[version, platform]` variant its
|
|
479
|
+
# gem was locked (or found installed) under, because Bundler installs the variant matching the host —
|
|
480
|
+
# `commonmarker-2.10.0-arm64-darwin/` under a lockfile whose representative is the `ruby` one (#1844).
|
|
481
|
+
def index_entries(rows, gems)
|
|
482
|
+
rows.map { |row| [row.gem_name, row.version, gems[row.gem_name]&.variants] }
|
|
458
483
|
end
|
|
459
484
|
|
|
460
485
|
def missing_rows(gems, default_libraries, bundle_sig_paths, rbs_collection_paths)
|
|
@@ -486,7 +486,17 @@ module Rigor
|
|
|
486
486
|
)
|
|
487
487
|
end
|
|
488
488
|
|
|
489
|
-
|
|
489
|
+
project_sequence_to_enumerator(self_type, other_type)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# `Enumerator::ArithmeticSequence[E]` carries only the element of its `Enumerator[Numeric, void]` superclass
|
|
493
|
+
# (#1794), so against a two-argument `Enumerator` it projects to `Enumerator[E, top]`, `top` being what RBS
|
|
494
|
+
# `void` translates to: `Enumerator[Integer, void]` and `[Integer, untyped]` accept it, as RBS would.
|
|
495
|
+
def project_sequence_to_enumerator(self_type, other_type)
|
|
496
|
+
return nil unless self_type.class_name == "Enumerator" && self_type.type_args.size == 2
|
|
497
|
+
return nil unless other_type.class_name == "Enumerator::ArithmeticSequence" && other_type.type_args.size == 1
|
|
498
|
+
|
|
499
|
+
Type::Combinator.nominal_of("Enumerator", type_args: [other_type.type_args.first, Type::Combinator.top])
|
|
490
500
|
end
|
|
491
501
|
|
|
492
502
|
def project_tuple_to_nominal(tuple)
|
|
@@ -963,11 +973,14 @@ module Rigor
|
|
|
963
973
|
end
|
|
964
974
|
end
|
|
965
975
|
|
|
966
|
-
# Constant[v] accepts
|
|
967
|
-
# (modulo the universal Bot/Dynamic short-circuits
|
|
976
|
+
# Constant[v] accepts Constant[v'] with structurally equal value, and the plain nominal of a class whose
|
|
977
|
+
# only instance is `v`. Any other type is rejected (modulo the universal Bot/Dynamic short-circuits
|
|
978
|
+
# already applied upstream).
|
|
968
979
|
def accepts_constant(self_type, other_type, mode)
|
|
969
980
|
if other_type.is_a?(Type::Constant) && self_type == other_type
|
|
970
981
|
Type::AcceptsResult.yes(mode: mode, reasons: "structural literal match")
|
|
982
|
+
elsif sole_instance_nominal?(self_type, other_type)
|
|
983
|
+
Type::AcceptsResult.yes(mode: mode, reasons: "#{other_type.class_name} has no instance but the literal")
|
|
971
984
|
else
|
|
972
985
|
Type::AcceptsResult.no(
|
|
973
986
|
mode: mode,
|
|
@@ -976,6 +989,18 @@ module Rigor
|
|
|
976
989
|
end
|
|
977
990
|
end
|
|
978
991
|
|
|
992
|
+
# `true`, `false` and `nil` are the only instances of their classes, so `FalseClass` holds exactly the
|
|
993
|
+
# values `false` holds. Read apart, `Array.new(n, false)`, typed `Array[FalseClass]` through the
|
|
994
|
+
# `[T] (Integer, T)` binding, failed a declared `-> Array[bool]` and fired `def.return-type-mismatch` on
|
|
995
|
+
# correct code.
|
|
996
|
+
SOLE_INSTANCE_CLASSES = { true => "TrueClass", false => "FalseClass", nil => "NilClass" }.freeze
|
|
997
|
+
private_constant :SOLE_INSTANCE_CLASSES
|
|
998
|
+
|
|
999
|
+
def sole_instance_nominal?(constant, other_type)
|
|
1000
|
+
other_type.is_a?(Type::Nominal) && other_type.type_args.empty? &&
|
|
1001
|
+
SOLE_INSTANCE_CLASSES.key?(constant.value) && SOLE_INSTANCE_CLASSES[constant.value] == other_type.class_name
|
|
1002
|
+
end
|
|
1003
|
+
|
|
979
1004
|
# Tuple[A1..An] accepts:
|
|
980
1005
|
# - Tuple[B1..Bn] when arities match and each Ai accepts Bi (covariant per-position).
|
|
981
1006
|
# - Anything else: no (we cannot prove the arity from a generic nominal alone).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require_relative "method_catalog"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module Builtins
|
|
9
|
+
# Issue #1740 — which instance methods CRuby itself defines on a core class or module, read from the offline
|
|
10
|
+
# catalogues under `data/builtins/ruby_core/` (`tool/extract_builtin_catalog.rb` records each `rb_define_method`
|
|
11
|
+
# and prelude `def` of an `Init_*` function on the class it names, aliases included). A registration inside an
|
|
12
|
+
# `#if 0` block CRuby keeps for RDoc carries `rdoc_only: true` and is left out: `random.c`'s registers
|
|
13
|
+
# `Random#seed` on `Random`, though it runs on `Random::Base`.
|
|
14
|
+
# `spec/rigor/inference/builtins/c_ruby_definers_spec.rb` checks every listed method the running Ruby has
|
|
15
|
+
# against its runtime owner. RBS is not that record: core RBS redeclares some inherited methods on a subclass
|
|
16
|
+
# (`Integer#quo` and `File#to_path`, whose CRuby owners are `Numeric` and `IO`), and declares a few CRuby no
|
|
17
|
+
# longer defines (`Process::Status#&`). A class no catalogue covers answers false, as does a method its
|
|
18
|
+
# catalogue does not list: the caller reads both as "not proven". The catalogue is one CRuby snapshot.
|
|
19
|
+
#
|
|
20
|
+
# Issue #1752 — the singleton side reads the catalogue's `singleton_methods` (each `rb_define_singleton_method`
|
|
21
|
+
# on the class), the record that `Integer.sqrt` is CRuby's own method of `Integer`'s singleton class.
|
|
22
|
+
module CRubyDefiners
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Does the catalogue prove CRuby defines `method_name` on `class_name` itself?
|
|
26
|
+
def defines?(class_name, method_name)
|
|
27
|
+
names = INDEX[class_name.to_s.delete_prefix("::")]
|
|
28
|
+
!names.nil? && names.include?(method_name.to_s)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Does the catalogue prove CRuby defines `method_name` on `class_name`'s singleton class itself?
|
|
32
|
+
def singleton_defines?(class_name, method_name)
|
|
33
|
+
names = SINGLETON_INDEX[class_name.to_s.delete_prefix("::")]
|
|
34
|
+
!names.nil? && names.include?(method_name.to_s)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# `{class name => Set[method name]}` from each catalogued class's instance methods and aliases, or from its
|
|
38
|
+
# `singleton_methods` when `side` is `:singleton`.
|
|
39
|
+
def build_index(side = :instance)
|
|
40
|
+
index = Hash.new { |hash, key| hash[key] = Set.new }
|
|
41
|
+
MethodCatalog.topic_paths.each do |path|
|
|
42
|
+
data = YAML.safe_load_file(path, permitted_classes: [Symbol])
|
|
43
|
+
classes = data.is_a?(Hash) ? data["classes"] : nil
|
|
44
|
+
next unless classes.is_a?(Hash)
|
|
45
|
+
|
|
46
|
+
classes.each do |name, klass|
|
|
47
|
+
next if klass["rdoc_only"]
|
|
48
|
+
|
|
49
|
+
merge_names(index[name], klass, side)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
Ractor.make_shareable(index.to_h { |name, names| [name, names.freeze] })
|
|
53
|
+
rescue Psych::SyntaxError, SystemCallError
|
|
54
|
+
Ractor.make_shareable({})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def merge_names(names, klass, side)
|
|
58
|
+
return names.merge(defined_names(klass["singleton_methods"])) if side == :singleton
|
|
59
|
+
|
|
60
|
+
names.merge(defined_names(klass["instance_methods"]))
|
|
61
|
+
names.merge(defined_names(klass["aliases"]))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The names of `entries` CRuby defines at runtime: one the catalogue marks `rdoc_only` was registered only for
|
|
65
|
+
# RDoc, inside an `#if 0` block.
|
|
66
|
+
def defined_names(entries)
|
|
67
|
+
return [] unless entries.is_a?(Hash)
|
|
68
|
+
|
|
69
|
+
entries.filter_map { |method, entry| method unless entry.is_a?(Hash) && entry["rdoc_only"] }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
INDEX = build_index
|
|
73
|
+
SINGLETON_INDEX = build_index(:singleton)
|
|
74
|
+
private_constant :INDEX, :SINGLETON_INDEX
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -48,6 +48,9 @@ module Rigor
|
|
|
48
48
|
new(path: File.join(DATA_ROOT, "#{topic}.yml"), mutating_selectors: mutating_selectors)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
# Every catalogue file under {DATA_ROOT}, in a stable order.
|
|
52
|
+
def self.topic_paths = Dir[File.join(DATA_ROOT, "*.yml")]
|
|
53
|
+
|
|
51
54
|
def initialize(path:, mutating_selectors: {})
|
|
52
55
|
@path = path
|
|
53
56
|
@mutating_selectors = mutating_selectors.transform_values(&:freeze).freeze
|
|
@@ -310,23 +310,63 @@ module Rigor
|
|
|
310
310
|
|
|
311
311
|
# The mutation sites under `body` whose reads name a variable {.captured_target?} accepts, keyed by name.
|
|
312
312
|
# The block yields the names the enclosing construct introduces, asked only when a local site is found.
|
|
313
|
+
#
|
|
314
|
+
# A site whose receiver is a body local that aliases a capture (`l2 = log; l2 << e`) mutates that capture's
|
|
315
|
+
# object, so it is attributed to the capture as well ({.aliased_captures}): left out, the next iteration
|
|
316
|
+
# read `log` at its entry `[]` and `[1, 2].inject(0) { |a, e| r = a + log.size; l2 = log; l2 << e; r }`
|
|
317
|
+
# folded to `0` where Ruby answers `1` (issue #1332).
|
|
313
318
|
def body_mutation_sites(body, base_scope, non_locals, &)
|
|
314
319
|
sites = nil
|
|
315
320
|
evaluator = nil
|
|
321
|
+
aliases = nil
|
|
316
322
|
Source::NodeWalker.each_with_ancestors(body) do |descendant, ancestors|
|
|
317
323
|
# A callee is resolved in the call-site scope, as the straight-line callee floor resolves it.
|
|
318
324
|
site = mutation_site(descendant) { evaluator ||= StatementEvaluator.new(scope: base_scope) }
|
|
319
325
|
next if site.nil?
|
|
320
326
|
|
|
321
327
|
site_reads(site).each do |read|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
328
|
+
targets = captured_target?(read, ancestors, base_scope, non_locals, &) ? [read] : []
|
|
329
|
+
if read.is_a?(Prism::LocalVariableReadNode)
|
|
330
|
+
aliases ||= local_aliases(body)
|
|
331
|
+
targets.concat(aliased_captures(read.name, aliases, base_scope, non_locals, Set.new, &))
|
|
332
|
+
end
|
|
333
|
+
targets.each { |target| ((sites ||= {})[target.name] ||= []) << site }
|
|
325
334
|
end
|
|
326
335
|
end
|
|
327
336
|
sites || NO_SITES
|
|
328
337
|
end
|
|
329
338
|
|
|
339
|
+
# `{ name => [[read, ancestors], ...] }`: every variable read a local write under `body` may store
|
|
340
|
+
# ({ReceiverAlias.candidates} of its value), with the ancestors {.captured_target?} resolves it against.
|
|
341
|
+
def local_aliases(body)
|
|
342
|
+
aliases = {}
|
|
343
|
+
Source::NodeWalker.each_with_ancestors(body) do |node, ancestors|
|
|
344
|
+
next unless node.is_a?(Prism::LocalVariableWriteNode) || node.is_a?(Prism::LocalVariableOrWriteNode)
|
|
345
|
+
|
|
346
|
+
ReceiverAlias.candidates(node.value).each do |read|
|
|
347
|
+
(aliases[node.name] ||= []) << [read, ancestors + [node]]
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
aliases
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# The captured reads the local `name` may alias, followed through local-to-local copies (`l3 = l2 = log`).
|
|
354
|
+
# It over-approximates — a write in any branch, or in a nested scope reusing the name, counts — which only
|
|
355
|
+
# widens a capture's binding.
|
|
356
|
+
def aliased_captures(name, aliases, base_scope, non_locals, visited, &)
|
|
357
|
+
return [] unless visited.add?(name)
|
|
358
|
+
|
|
359
|
+
aliases.fetch(name, NO_NAMES).flat_map do |read, ancestors|
|
|
360
|
+
if captured_target?(read, ancestors, base_scope, non_locals, &)
|
|
361
|
+
[read]
|
|
362
|
+
elsif read.is_a?(Prism::LocalVariableReadNode)
|
|
363
|
+
aliased_captures(read.name, aliases, base_scope, non_locals, visited, &)
|
|
364
|
+
else
|
|
365
|
+
NO_NAMES
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
330
370
|
# The mutation site `node` is — the node itself, or a {UnknownStoreWidening::CalleeStore} for a self-call
|
|
331
371
|
# whose callee content-mutates a local it is passed — or nil. A self-call is resolved by the
|
|
332
372
|
# `StatementEvaluator` the block yields, which the caller builds only when one is needed. A mutator name
|
|
@@ -33,7 +33,7 @@ module Rigor
|
|
|
33
33
|
# ({.rehydrated_nesting}).
|
|
34
34
|
def self.with_run
|
|
35
35
|
previous = Thread.current[MEMO_KEY]
|
|
36
|
-
Thread.current[MEMO_KEY] = { nodes: {}, indexes: {}, nestings: {}.compare_by_identity }
|
|
36
|
+
Thread.current[MEMO_KEY] = { nodes: {}, indexes: {}, nestings: {}.compare_by_identity, refinement_queries: {} }
|
|
37
37
|
yield
|
|
38
38
|
ensure
|
|
39
39
|
Thread.current[MEMO_KEY] = previous
|
|
@@ -113,6 +113,28 @@ module Rigor
|
|
|
113
113
|
end
|
|
114
114
|
private_class_method :record_nesting
|
|
115
115
|
|
|
116
|
+
# Issue #1664 — the {InEffectRefinements} over `path`'s own parse, once a run, or nil when the file cannot be
|
|
117
|
+
# read: the typed refined arm reads another file's refine body through it. The bodies' `Module.nesting`
|
|
118
|
+
# is recorded as a handle's is, so the re-typed body reads its constants where it is written.
|
|
119
|
+
def self.refinement_query(path)
|
|
120
|
+
memo = Thread.current[MEMO_KEY]
|
|
121
|
+
return build_refinement_query(path, nil) if memo.nil?
|
|
122
|
+
|
|
123
|
+
queries = memo[:refinement_queries]
|
|
124
|
+
return queries[path] if queries.key?(path)
|
|
125
|
+
|
|
126
|
+
queries[path] = build_refinement_query(path, memo)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.build_refinement_query(path, memo)
|
|
130
|
+
query = InEffectRefinements.new(Prism.parse(File.read(path)).value)
|
|
131
|
+
query.refine_def_nestings.each { |node, nesting| record_nesting(memo, node, nesting) } if memo
|
|
132
|
+
query
|
|
133
|
+
rescue StandardError
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
private_class_method :build_refinement_query
|
|
137
|
+
|
|
116
138
|
# Finds the node for `handle` using a per-file `{node_id => DefNode}` + `{name => DefNode}` index cache.
|
|
117
139
|
def self.locate(handle, index_cache)
|
|
118
140
|
index = index_cache[handle.path] ||= build_file_index(handle.path)
|