rigortype 0.4.7 → 0.4.8
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 +3 -1
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +29 -0
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/docs/handbook/01-getting-started.md +1 -1
- data/docs/handbook/11-sig-gen.md +35 -0
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +307 -91
- data/docs/manual/03-configuration.md +41 -3
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +8 -0
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +15 -6
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +598 -99
- data/lib/rigor/analysis/crash_signature.rb +43 -1
- data/lib/rigor/analysis/dependency_recorder.rb +233 -76
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +279 -37
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +37 -1
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
- data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
- data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner.rb +222 -39
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
- data/lib/rigor/analysis/worker_session.rb +42 -41
- data/lib/rigor/bleeding_edge.rb +19 -4
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +66 -7
- data/lib/rigor/cache/rbs_descriptor.rb +37 -2
- data/lib/rigor/cache/rbs_environment.rb +5 -1
- data/lib/rigor/cli/baseline_command.rb +15 -2
- data/lib/rigor/cli/check_command.rb +4 -2
- data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
- data/lib/rigor/cli/lens_command.rb +17 -2
- data/lib/rigor/cli/sig_gen_command.rb +8 -0
- data/lib/rigor/cli/skill_describe.rb +1 -0
- data/lib/rigor/cli.rb +5 -2
- data/lib/rigor/config_audit.rb +32 -18
- data/lib/rigor/configuration/severity_profile.rb +9 -0
- data/lib/rigor/configuration.rb +32 -5
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/default_libraries.rb +17 -0
- data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
- data/lib/rigor/environment/rbs_loader.rb +312 -63
- data/lib/rigor/environment.rb +131 -35
- data/lib/rigor/inference/acceptance.rb +50 -15
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_run_count.rb +9 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +56 -15
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/def_node_resolver.rb +32 -14
- data/lib/rigor/inference/def_return_typer.rb +1 -1
- data/lib/rigor/inference/definer_resolution.rb +15 -7
- data/lib/rigor/inference/definers_index.rb +72 -16
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1459 -287
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +8 -6
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +57 -8
- data/lib/rigor/inference/ivar_writer_census.rb +106 -11
- data/lib/rigor/inference/key_presence_guard.rb +51 -25
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +63 -15
- data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
- data/lib/rigor/inference/match_rebinding.rb +21 -8
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher.rb +66 -18
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +13 -2
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_widening.rb +44 -4
- data/lib/rigor/inference/narrowing.rb +201 -27
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +133 -8
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +28 -10
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +76 -22
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2079 -183
- data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +970 -250
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
- data/lib/rigor/inference/toplevel_def_census.rb +14 -6
- data/lib/rigor/inference/unknown_store_widening.rb +1 -1
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -10
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/io_boundary.rb +25 -3
- data/lib/rigor/plugin/manifest.rb +7 -3
- data/lib/rigor/plugin/registry.rb +13 -2
- data/lib/rigor/plugin/trust_policy.rb +46 -0
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/reflection/constant_ancestors.rb +79 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +18 -1
- data/lib/rigor/run_scoped_slots.rb +20 -20
- data/lib/rigor/scope/discovery_index.rb +149 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +900 -55
- data/lib/rigor/scope.rb +307 -34
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +14 -1
- data/lib/rigor/sig_gen/generator.rb +222 -13
- data/lib/rigor/sig_gen/method_candidate.rb +14 -0
- data/lib/rigor/sig_gen/observation_collector.rb +11 -2
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
- data/lib/rigor/sig_gen/writer.rb +21 -0
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/combinator.rb +164 -8
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -3
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/plugin/base.rbs +53 -19
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -3
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +22 -3
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +48 -2
- data/lib/rigor/inference/memo_audit.rb +0 -55
data/lib/rigor/environment.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative "environment/bundle_sig_discovery"
|
|
|
16
16
|
require_relative "environment/lockfile_resolver"
|
|
17
17
|
require_relative "environment/installed_gem_set"
|
|
18
18
|
require_relative "environment/rbs_collection_discovery"
|
|
19
|
+
require_relative "library_probe"
|
|
19
20
|
require_relative "environment/project_path_gems"
|
|
20
21
|
require_relative "environment/source_rbs_synthesis"
|
|
21
22
|
require_relative "plugin/isolation"
|
|
@@ -49,6 +50,19 @@ module Rigor
|
|
|
49
50
|
# `RbsCoverageReport` reports.
|
|
50
51
|
GEM_OVERLAY_PLUGIN_IDS = { "activesupport" => "activesupport-core-ext" }.freeze
|
|
51
52
|
|
|
53
|
+
# Issue #2857 — the `RbsCoverageReport` sources each gem's bundled overlay may load under, when they
|
|
54
|
+
# differ from the ADR-72 `:missing` default every unlisted overlay keeps. `addressable` is the first
|
|
55
|
+
# exception: its overlay exists to CORRECT the `Addressable::URI.parse` signature `rbs collection`
|
|
56
|
+
# ships (`parse(nil)` returns nil; the collection declares `(String | URI) -> URI`), so it loads only
|
|
57
|
+
# under `:rbs_collection` coverage, where the deferred-signature plan composes it over the collection
|
|
58
|
+
# copy as an overload continuation. Under `:missing` coverage — no sigs at all — it stays unloaded:
|
|
59
|
+
# the overlay's partial `Addressable::URI` declaration would make every member it omits a false
|
|
60
|
+
# `call.undefined-method`, a worse answer than the `Dynamic` silence a sig-less gem already has.
|
|
61
|
+
# Frozen arrays, not Sets: `Ractor.shareable?` rejects a Set even frozen (ractor_readiness_spec).
|
|
62
|
+
GEM_OVERLAY_COVERAGE_SOURCES = { "addressable" => [:rbs_collection].freeze }.freeze
|
|
63
|
+
GEM_OVERLAY_DEFAULT_COVERAGE_SOURCES = [:missing].freeze
|
|
64
|
+
private_constant :GEM_OVERLAY_DEFAULT_COVERAGE_SOURCES
|
|
65
|
+
|
|
52
66
|
attr_reader :class_registry, :rbs_loader, :plugin_registry, :dependency_source_index,
|
|
53
67
|
:reporters, :name_scope,
|
|
54
68
|
:synthetic_method_index, :project_patched_methods
|
|
@@ -118,6 +132,13 @@ module Rigor
|
|
|
118
132
|
# stage that failed and the other stage still runs, so this getter never raises into its callers —
|
|
119
133
|
# neither into a file's `analyze_body` rescue nor out of the run-owned demands, which have no rescue
|
|
120
134
|
# above them at all.
|
|
135
|
+
# Issue #2883 — builds the RBS environment now, if it is not built yet, so typing never builds it. A no-op for an
|
|
136
|
+
# environment with no loader. Answers the environment.
|
|
137
|
+
def prepare_for_typing!
|
|
138
|
+
rbs_loader&.prepare_for_typing!
|
|
139
|
+
self
|
|
140
|
+
end
|
|
141
|
+
|
|
121
142
|
def hkt_registry
|
|
122
143
|
@hkt_registry_holder.fetch do
|
|
123
144
|
pre_scan = pre_scan_hkt_registry
|
|
@@ -247,11 +268,14 @@ module Rigor
|
|
|
247
268
|
end
|
|
248
269
|
|
|
249
270
|
class << self
|
|
271
|
+
# Issue #2883 — the shared default environment is built here too, not on the first `type_of` that needs it, so
|
|
272
|
+
# code that types against it (`Scope.empty`, the narrowing defaults, the incremental session's summary
|
|
273
|
+
# evaluator) never builds it from inside typing. A non-main Ractor keeps the lazy build.
|
|
250
274
|
def default
|
|
251
275
|
@default ||= new(
|
|
252
276
|
rbs_loader: RbsLoader.default,
|
|
253
277
|
hkt_registry: Builtins::HktBuiltins.registry
|
|
254
|
-
).freeze
|
|
278
|
+
).freeze.tap { |environment| environment.prepare_for_typing! if Ractor.main? }
|
|
255
279
|
end
|
|
256
280
|
|
|
257
281
|
# Builds an Environment that consults the project's local signatures and any opt-in stdlib libraries on
|
|
@@ -270,6 +294,10 @@ module Rigor
|
|
|
270
294
|
# caching for this environment.
|
|
271
295
|
# @param core_root — issue #2517 — a core signature directory loaded in place of the `rbs` gem's `core/`
|
|
272
296
|
# ({Configuration#core_root}); nil (the default) loads the gem's core.
|
|
297
|
+
# @param eager — issue #2883 — whether the RBS environment is built here, before anything types. Typing then
|
|
298
|
+
# never builds it, so the core-root Frame the build reads (ADR-129 G1') is never read inside a `type_of`. The
|
|
299
|
+
# one caller that passes `false` is the runner's pre-cache build, which a warm result-cache hit must not pay
|
|
300
|
+
# for; it calls {#prepare_for_typing!} on the analysis path.
|
|
273
301
|
# rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists
|
|
274
302
|
def for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil, # rubocop:disable Metrics/AbcSize
|
|
275
303
|
plugin_registry: nil, dependency_source_index: nil,
|
|
@@ -280,7 +308,7 @@ module Rigor
|
|
|
280
308
|
rbs_collection_lockfile: nil, rbs_collection_auto_detect: false,
|
|
281
309
|
synthetic_method_index: nil, project_patched_methods: nil,
|
|
282
310
|
source_files: [], locked_gems: nil, required_features: nil, core_root: nil,
|
|
283
|
-
paths: nil, exclude_patterns: nil, exclude_base: nil)
|
|
311
|
+
paths: nil, exclude_patterns: nil, exclude_base: nil, eager: true)
|
|
284
312
|
resolved_paths = project_signature_roots(signature_paths, root: root)
|
|
285
313
|
# O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
|
|
286
314
|
# root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
|
|
@@ -313,14 +341,17 @@ module Rigor
|
|
|
313
341
|
)
|
|
314
342
|
# O4 Layer 3 slice 2 — when `rbs collection install` has been run for the target project, parse the
|
|
315
343
|
# resulting `rbs_collection.lock.yaml` and feed each gem's `<collection_path>/<name>/<version>/`
|
|
316
|
-
# directory into `signature_paths:`. Stdlib-typed entries
|
|
317
|
-
#
|
|
344
|
+
# directory into `signature_paths:`. Stdlib-typed entries have no such directory; they load as libraries
|
|
345
|
+
# (issue #2946, below). Skipped too are gems whose RBS rigor already loads from another source —
|
|
318
346
|
# stdlib-extracted default gems (`cgi`, `logger`, …, shipped by the collection under a `git` source)
|
|
319
347
|
# and the `data/vendored_gem_sigs/` bundle (`redis`, `nokogiri`, `pg`, …). `skip_gem_names:` passes
|
|
320
348
|
# both sets so the collection copy doesn't double-declare against rigor's bundled RBS (the
|
|
321
349
|
# `RBS::DuplicatedDeclarationError` hazard).
|
|
322
350
|
# Issue #1700 — plus the gated vendored directories this run's source activates ({#feature_tokens}).
|
|
323
|
-
|
|
351
|
+
# Issue #2946 — plus the lockfile's `source: { type: stdlib }` entries, as libraries ({BaseLibraries.resolve}).
|
|
352
|
+
merged_libraries = (BaseLibraries.resolve(libraries, rbs_collection_lockfile: rbs_collection_lockfile,
|
|
353
|
+
rbs_collection_auto_detect: rbs_collection_auto_detect,
|
|
354
|
+
root: root) +
|
|
324
355
|
feature_tokens(required_features, source_files, libraries)).uniq
|
|
325
356
|
# Issue #2441 — plus the project's own path gems ({ProjectPathGems}), whose collection copy would
|
|
326
357
|
# double-declare the project's `sig/`.
|
|
@@ -333,24 +364,32 @@ module Rigor
|
|
|
333
364
|
auto_detect: rbs_collection_auto_detect,
|
|
334
365
|
skip_gem_names: skip_gem_names
|
|
335
366
|
).map(&:to_s)
|
|
367
|
+
# Issue #3030 — a gem the project's `signature_paths:` already loads a copy of (a gem_rbs_collection working
|
|
368
|
+
# tree next to its `_test/` lockfile) loads from there alone. The coverage classification below still sees
|
|
369
|
+
# the whole list: the gem has RBS either way.
|
|
370
|
+
covered_collection_paths = collection_paths
|
|
371
|
+
collection_paths = RbsCollectionDiscovery.without_signature_path_copies(collection_paths, resolved_paths)
|
|
372
|
+
# Issue #2978 — the collection copies of the locked gems whose own `sig/` the bundle discovery loaded.
|
|
373
|
+
behind_gem_sig_paths = collection_paths_behind_gem_sigs(collection_paths, gem_sig_paths, locked)
|
|
336
374
|
# ADR-25 — RBS signature directories contributed by loaded plugins via their manifest
|
|
337
375
|
# `signature_paths:`. Resolved to absolute dirs by `Plugin::Base#signature_paths`; additive, ranked
|
|
338
376
|
# below the user's explicit `signature_paths:` and above the opportunistic bundle / collection
|
|
339
377
|
# discovery. A duplicate-declaration conflict degrades through the same O7 failure-memo path.
|
|
340
378
|
plugin_sig_paths = plugin_registry ? plugin_registry.signature_paths.map(&:to_s) : []
|
|
341
|
-
# ADR-72 — Gemfile.lock-gated bundled RBS overlays. For each locked gem
|
|
342
|
-
#
|
|
343
|
-
#
|
|
344
|
-
#
|
|
345
|
-
#
|
|
346
|
-
#
|
|
347
|
-
#
|
|
348
|
-
#
|
|
379
|
+
# ADR-72 — Gemfile.lock-gated bundled RBS overlays. For each locked gem whose coverage source its
|
|
380
|
+
# overlay may load under ({GEM_OVERLAY_COVERAGE_SOURCES} — `:missing` for every overlay but
|
|
381
|
+
# addressable's `:rbs_collection` correction, #2857) and that has a Rigor-bundled overlay, load that
|
|
382
|
+
# overlay so its core-class extensions resolve (e.g. `Integer#minutes` on a Rails project) —
|
|
383
|
+
# turning a systematic false `call.undefined-method` into a no-op while a project WITHOUT the gem
|
|
384
|
+
# still sees the genuine diagnostic. Appended last so any RBS the project already supplies wins,
|
|
385
|
+
# and skipped for a gem whose opt-in plugin twin is REACHABLE — loaded as a plugin, or wired in by
|
|
386
|
+
# `signature_paths:` (#672) — so the two never both declare the methods. The paths ride in
|
|
387
|
+
# `loader_signature_paths`, so the env cache descriptor digests them for free.
|
|
349
388
|
# Issue #2441 — the project's own path gems are neither missing RBS nor overlay-eligible: the project's
|
|
350
389
|
# `sig/` declares them, and an overlay (`data/gem_overlay/activesupport`) would collide with it.
|
|
351
390
|
missing_gems, overlay_paths = missing_gems_and_overlay_paths(
|
|
352
391
|
locked: locked.except(*project_gems), default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
|
|
353
|
-
rbs_collection_paths:
|
|
392
|
+
rbs_collection_paths: covered_collection_paths, plugin_registry: plugin_registry,
|
|
354
393
|
signature_paths: resolved_paths, bundle_root: bundle_root
|
|
355
394
|
)
|
|
356
395
|
loader_signature_paths = resolved_paths + plugin_sig_paths + gem_sig_paths +
|
|
@@ -374,8 +413,17 @@ module Rigor
|
|
|
374
413
|
# Issue #610 — the plugin-contributed subset is loaded LAST and may stand down against a colliding
|
|
375
414
|
# generic arity. Only the bundled plugins' own `sig/` is deferred: the user's `signature_paths:`,
|
|
376
415
|
# the bundle walk and `rbs collection install` all stay authoritative.
|
|
377
|
-
|
|
416
|
+
# Issue #2974 — the ADR-72 gem overlays are Rigor-shipped too, and deferred with them: an overlay member
|
|
417
|
+
# the project's `sig/` also declares (`Integer#ordinalize`, `ActiveSupport::NumericWithFormat#to_fs`)
|
|
418
|
+
# stands down member by member (#2422) instead of failing the class's definition build.
|
|
419
|
+
deferred_signature_paths: plugin_sig_paths + overlay_paths,
|
|
378
420
|
project_signature_paths: resolved_paths,
|
|
421
|
+
# Issue #2978 — a gem's collection copy loads behind the gem's own `sig/`: each member that `sig/` (or any
|
|
422
|
+
# other eagerly loaded signature file) declares stands down from the copy (#2422), and the rest loads.
|
|
423
|
+
subordinate_signature_paths: behind_gem_sig_paths,
|
|
424
|
+
# Issue #3030 — the collection gem directories, loaded as rbs loads a library: without the files below a
|
|
425
|
+
# `_`-prefixed directory (each gem's `_test/` fixtures).
|
|
426
|
+
collection_signature_paths: collection_paths,
|
|
379
427
|
required_features: required_features || RequiredFeatures.scan(source_files, sig_paths: resolved_paths),
|
|
380
428
|
core_root: core_root
|
|
381
429
|
)
|
|
@@ -390,7 +438,9 @@ module Rigor
|
|
|
390
438
|
# Issue #2517 — a selected core is built here, not lazily. The env is first demanded from inside a file's
|
|
391
439
|
# analysis, under rescues that turn any raise into one `internal analyzer error` row per file; building it
|
|
392
440
|
# now raises the {CoreRootError} once, before analysis, where the CLI reports it as a configuration error.
|
|
393
|
-
|
|
441
|
+
# Issue #2883 — the default core is built here too (`eager:`), so no `type_of` builds the environment.
|
|
442
|
+
# A non-main Ractor worker keeps the lazy build: the `rbs` gem's unshareable constants are unreadable there.
|
|
443
|
+
eager && Ractor.main? ? loader.prepare_for_typing! : loader.verify_core_root!
|
|
394
444
|
# ADR-20 slice 2c + 2e — seed hkt_registry with the bundled builtins. The Environment's
|
|
395
445
|
# `#hkt_registry` getter then LAZILY merges in the RBS env scan on first call so fast paths that
|
|
396
446
|
# don't consult HKT (e.g. `rigor check --cache-stats --no-stats`) don't pay the eager env-build cost
|
|
@@ -414,10 +464,20 @@ module Rigor
|
|
|
414
464
|
# The project-owned `sig/` roots a run reads: the explicit `signature_paths:` when given, else the
|
|
415
465
|
# auto-detected `<root>/sig` (or none). Public so {Cache::IncrementalSnapshot} digests exactly the
|
|
416
466
|
# directories the environment loads, including the auto-detected one a `nil` `signature_paths:` hides.
|
|
467
|
+
# The default branch is {project_signature_root_candidates} narrowed to the directories that exist.
|
|
417
468
|
def project_signature_roots(signature_paths, root: Dir.pwd)
|
|
418
469
|
signature_paths || default_signature_paths(root)
|
|
419
470
|
end
|
|
420
471
|
|
|
472
|
+
# Issue #1619 — the single spelling of "the configured `signature_paths:`, else the conventional `sig`
|
|
473
|
+
# directory", as the CANDIDATES a run watches whether or not they exist yet. {project_signature_roots}
|
|
474
|
+
# narrows this to what the loader reads (a default only once it is a directory); the run slot's presence
|
|
475
|
+
# rows and the incremental write guard take it whole, because a `sig/` appearing mid-run must move the
|
|
476
|
+
# fingerprint and refuse the slot write — a question only a row for a path that need not exist can ask.
|
|
477
|
+
def project_signature_root_candidates(signature_paths)
|
|
478
|
+
signature_paths || [DEFAULT_PROJECT_SIG_DIR]
|
|
479
|
+
end
|
|
480
|
+
|
|
421
481
|
private
|
|
422
482
|
|
|
423
483
|
# Issue #1700 — the `RequiredFeatures` library tokens for the gated vendored directories this run asks for:
|
|
@@ -431,13 +491,31 @@ module Rigor
|
|
|
431
491
|
# The gem names an `rbs collection` copy must not double-declare against: the library list and every
|
|
432
492
|
# vendored directory except a `RequiredFeatures`-gated one. A gated directory stands down for any other
|
|
433
493
|
# copy of its declarations (`GatedSignatureGuard`), so the collection's copy is kept and wins.
|
|
494
|
+
#
|
|
495
|
+
# Issue #2971 — a `libraries:` name that is not an RBS library (`foogem`, collection-installed) is dropped by
|
|
496
|
+
# the loader's `has_library?` filter, so it declares nothing to double-declare against; it stays out of the
|
|
497
|
+
# skip set so the collection's copy still loads. Issue #2978 — that holds for a gem whose own `sig/` the bundle
|
|
498
|
+
# loaded too: its collection copy loads behind that `sig/` ({.collection_paths_behind_gem_sigs}).
|
|
434
499
|
def collection_skip_gem_names(library_names)
|
|
435
|
-
library_names + RbsLoader.vendored_gem_names.reject
|
|
500
|
+
LibraryProbe.loadable(library_names) + RbsLoader.vendored_gem_names.reject do |name|
|
|
501
|
+
RequiredFeatures.gated_dir?(name)
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Issue #2978 — the `collection_paths` (`<collection>/<name>/<version>/`) of the locked gems whose own `sig/` is
|
|
506
|
+
# among `gem_sig_paths` ({BundleSigDiscovery.locked_names_of}, keyed by name and version).
|
|
507
|
+
def collection_paths_behind_gem_sigs(collection_paths, gem_sig_paths, locked)
|
|
508
|
+
return [] if collection_paths.empty? || gem_sig_paths.empty?
|
|
509
|
+
|
|
510
|
+
names = BundleSigDiscovery.locked_names_of(gem_sig_paths.map { |dir| Pathname.new(dir) }, locked).to_set
|
|
511
|
+
collection_paths.select { |path| names.include?(Pathname.new(path).parent.basename.to_s) }
|
|
436
512
|
end
|
|
437
513
|
|
|
438
514
|
def default_signature_paths(root)
|
|
439
|
-
|
|
440
|
-
|
|
515
|
+
project_signature_root_candidates(nil).filter_map do |candidate|
|
|
516
|
+
path = Pathname(root) / candidate.to_s
|
|
517
|
+
path if path.directory?
|
|
518
|
+
end
|
|
441
519
|
end
|
|
442
520
|
|
|
443
521
|
# ADR-82 WD9 — the resolved bundle root for the missing-gem constant index, doubling (ADR-90) as the
|
|
@@ -460,9 +538,12 @@ module Rigor
|
|
|
460
538
|
bundle_root
|
|
461
539
|
end
|
|
462
540
|
|
|
463
|
-
# The
|
|
464
|
-
# and consumed twice:
|
|
465
|
-
# index, and
|
|
541
|
+
# The per-gem RBS coverage classification (locked gems against every resolution path), computed
|
|
542
|
+
# once and consumed twice: its `:missing` rows become `[gem_name, version, variants]` entries for
|
|
543
|
+
# the ADR-82 WD9 constant-ownership index, and the full row set feeds the ADR-72 overlay resolution,
|
|
544
|
+
# which admits a gem when its row's source is one the gem's overlay may load under
|
|
545
|
+
# ({GEM_OVERLAY_COVERAGE_SOURCES} — `:missing` by default, `:rbs_collection` for addressable).
|
|
546
|
+
# Returns `[entries, overlay_paths]`.
|
|
466
547
|
def missing_gems_and_overlay_paths(locked:, default_libraries:, bundle_sig_paths:,
|
|
467
548
|
rbs_collection_paths:, plugin_registry:, signature_paths: [],
|
|
468
549
|
bundle_root: nil)
|
|
@@ -470,10 +551,10 @@ module Rigor
|
|
|
470
551
|
return unlocked_missing_gems(default_libraries, bundle_sig_paths, rbs_collection_paths, bundle_root)
|
|
471
552
|
end
|
|
472
553
|
|
|
473
|
-
|
|
554
|
+
coverage = coverage_rows(locked, default_libraries, bundle_sig_paths, rbs_collection_paths)
|
|
555
|
+
rows = coverage.select { |row| row.source == :missing }
|
|
474
556
|
overlays = gem_overlay_paths(
|
|
475
|
-
|
|
476
|
-
signature_paths: signature_paths
|
|
557
|
+
coverage: coverage, plugin_registry: plugin_registry, signature_paths: signature_paths
|
|
477
558
|
)
|
|
478
559
|
[index_entries(rows, locked), overlays]
|
|
479
560
|
end
|
|
@@ -500,14 +581,20 @@ module Rigor
|
|
|
500
581
|
end
|
|
501
582
|
|
|
502
583
|
def missing_rows(gems, default_libraries, bundle_sig_paths, rbs_collection_paths)
|
|
584
|
+
coverage_rows(gems, default_libraries, bundle_sig_paths, rbs_collection_paths)
|
|
585
|
+
.select { |row| row.source == :missing }
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def coverage_rows(gems, default_libraries, bundle_sig_paths, rbs_collection_paths)
|
|
503
589
|
RbsCoverageReport.classify(
|
|
504
590
|
locked_gems: gems, default_libraries: default_libraries,
|
|
505
591
|
bundle_sig_paths: bundle_sig_paths, rbs_collection_paths: rbs_collection_paths
|
|
506
|
-
)
|
|
592
|
+
)
|
|
507
593
|
end
|
|
508
594
|
|
|
509
595
|
# ADR-72 — resolve the bundled RBS overlay directories to load for this project. A gem is eligible when
|
|
510
|
-
#
|
|
596
|
+
# its coverage row's source is one the gem's overlay may load under ({GEM_OVERLAY_COVERAGE_SOURCES};
|
|
597
|
+
# `:missing` for every overlay the map does not name) and its conflicting opt-in plugin twin (if
|
|
511
598
|
# any) is NOT already reachable. Returns `[Pathname]`, deterministically ordered, or `[]` when no
|
|
512
599
|
# eligible gem.
|
|
513
600
|
#
|
|
@@ -521,20 +608,29 @@ module Rigor
|
|
|
521
608
|
# all); `RbsLoader.bundled_overlay_twin_signatures_loaded?` answers the filesystem half, beside the
|
|
522
609
|
# overlay-side answer to the same question, and carries the reasoning for why it asks what an entry
|
|
523
610
|
# LOADS rather than what it looks like.
|
|
524
|
-
def gem_overlay_paths(
|
|
525
|
-
return [] if
|
|
611
|
+
def gem_overlay_paths(coverage:, plugin_registry:, signature_paths: [])
|
|
612
|
+
return [] if coverage.empty?
|
|
526
613
|
|
|
527
614
|
loaded_ids = plugin_registry ? plugin_registry.ids.to_set : Set.new
|
|
528
|
-
eligible =
|
|
529
|
-
|
|
530
|
-
|
|
615
|
+
eligible = coverage.filter_map do |row|
|
|
616
|
+
next unless overlay_coverage_source?(row.gem_name, row.source)
|
|
617
|
+
|
|
618
|
+
plugin_id = GEM_OVERLAY_PLUGIN_IDS[row.gem_name]
|
|
619
|
+
next if plugin_id &&
|
|
620
|
+
(loaded_ids.include?(plugin_id) ||
|
|
621
|
+
RbsLoader.bundled_overlay_twin_signatures_loaded?(plugin_id, signature_paths))
|
|
531
622
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
end.sort
|
|
623
|
+
row.gem_name
|
|
624
|
+
end.uniq.sort
|
|
535
625
|
RbsLoader.gem_overlay_sig_paths(eligible)
|
|
536
626
|
end
|
|
537
627
|
|
|
628
|
+
# Whether `source` — a `RbsCoverageReport` source — is one `gem_name`'s bundled overlay may load
|
|
629
|
+
# under: the gem's {GEM_OVERLAY_COVERAGE_SOURCES} entry when it has one, else ADR-72's `:missing`.
|
|
630
|
+
def overlay_coverage_source?(gem_name, source)
|
|
631
|
+
(GEM_OVERLAY_COVERAGE_SOURCES[gem_name] || GEM_OVERLAY_DEFAULT_COVERAGE_SOURCES).include?(source)
|
|
632
|
+
end
|
|
633
|
+
|
|
538
634
|
# ADR-32 WD4 + WD5 — for each project source file, invoke every plugin-registered synthesizer once and
|
|
539
635
|
# collect non-nil returns. The returned array is `[[virtual_filename, rbs_source], ...]`; the loader
|
|
540
636
|
# threads it through to `RbsLoader.new(virtual_rbs: ...)`.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../type"
|
|
4
4
|
require_relative "range_constant"
|
|
5
5
|
require_relative "content_join"
|
|
6
|
+
require_relative "../typing_state"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Inference
|
|
@@ -75,8 +76,16 @@ module Rigor
|
|
|
75
76
|
}.freeze
|
|
76
77
|
private_constant :TYPE_HANDLERS
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
# ADR-129 WD1 — a Frame: {#refusing_unread} installs the kind of unread shape that answers `no` for one block
|
|
80
|
+
# and restores the previous one on the way out. It changes answers (`unread_shape_maybe` reads it), and the
|
|
81
|
+
# installed value is a Symbol (`:all`, `:array`) or nil.
|
|
82
|
+
UNREAD_SHAPE_REFUSED = TypingState.frame(
|
|
83
|
+
:acceptance_unread_shape_refused,
|
|
84
|
+
reason: "installed around a block by refusing_unread; read by unread_shape_maybe, which answers no for " \
|
|
85
|
+
"the refused kind of unread shape instead of maybe",
|
|
86
|
+
witness: "spec/fixtures/typing_state_witnesses/acceptance_unread_shape_refused.rb"
|
|
87
|
+
)
|
|
88
|
+
private_constant :UNREAD_SHAPE_REFUSED
|
|
80
89
|
|
|
81
90
|
# Whether a `maybe` for `param` against `arg` rests on a shape that could not read its argument: a record
|
|
82
91
|
# against a `Hash` with a gradual arm ({#gradual_hash?}) or an open `HashShape`, or a tuple against an
|
|
@@ -120,18 +129,14 @@ module Rigor
|
|
|
120
129
|
# calls below the one asked (a tuple element, a `Hash` value, a union member), and those calls go
|
|
121
130
|
# through each carrier's `Type#accepts`. It is set only for the duration of the block. `kind` is `:all`
|
|
122
131
|
# (records and tuples) or `:array` (tuples only).
|
|
123
|
-
def refusing_unread(kind)
|
|
124
|
-
|
|
125
|
-
Thread.current[UNREAD_SHAPE_REFUSED_KEY] = kind
|
|
126
|
-
yield
|
|
127
|
-
ensure
|
|
128
|
-
Thread.current[UNREAD_SHAPE_REFUSED_KEY] = previous
|
|
132
|
+
def refusing_unread(kind, &)
|
|
133
|
+
TypingState.current.with(UNREAD_SHAPE_REFUSED, kind, &)
|
|
129
134
|
end
|
|
130
135
|
|
|
131
136
|
# The `maybe` a shape of `kind` (`:hash` or `:array`) answers for an argument it cannot read, or `no`
|
|
132
137
|
# inside {#refusing_unread} for that kind.
|
|
133
138
|
def unread_shape_maybe(mode, reason, kind)
|
|
134
|
-
refused =
|
|
139
|
+
refused = TypingState.current[UNREAD_SHAPE_REFUSED]
|
|
135
140
|
if refused == :all || refused == kind
|
|
136
141
|
return Type::AcceptsResult.no(mode: mode, reasons: "#{reason} (refused as evidence)")
|
|
137
142
|
end
|
|
@@ -427,6 +432,19 @@ module Rigor
|
|
|
427
432
|
CLASS_OBJECT_NOMINALS = %w[Module Class Object Kernel BasicObject].freeze
|
|
428
433
|
private_constant :CLASS_OBJECT_NOMINALS
|
|
429
434
|
|
|
435
|
+
# Issue #2855 — whether `type` is an unparameterised `Class` or `Module` nominal: a class object whose
|
|
436
|
+
# identity inference lost (`const_get` kept by `is_a?(Class)`, an `is_a?(Module)` guard). `Acceptance` still
|
|
437
|
+
# answers `no` for it against a `Singleton` (it is a supertype, not a subtype); `call.argument-type-mismatch`
|
|
438
|
+
# reads it to withhold that verdict. `Object`, `BasicObject` and `Kernel` are left out: they mean "any value",
|
|
439
|
+
# and `Object.new` against `singleton(X)` is a real mismatch.
|
|
440
|
+
CLASS_OR_MODULE_NOMINALS = %w[Class Module].freeze
|
|
441
|
+
private_constant :CLASS_OR_MODULE_NOMINALS
|
|
442
|
+
|
|
443
|
+
def class_or_module_nominal?(type)
|
|
444
|
+
type.is_a?(Type::Nominal) && type.type_args.empty? && CLASS_OR_MODULE_NOMINALS.include?(type.class_name)
|
|
445
|
+
end
|
|
446
|
+
public :class_or_module_nominal?
|
|
447
|
+
|
|
430
448
|
def accepts_nominal_from_singleton(self_type, other_type, mode)
|
|
431
449
|
if CLASS_OBJECT_NOMINALS.include?(self_type.class_name)
|
|
432
450
|
return Type::AcceptsResult.yes(
|
|
@@ -850,11 +868,14 @@ module Rigor
|
|
|
850
868
|
# carrier whose value set is visibly apart from `removed` — a `Constant` (compare values directly),
|
|
851
869
|
# another `Difference` with the same removed value (already exhibits the disjointness), an
|
|
852
870
|
# `IntegerRange` that skips an Integer removed value, a `Tuple` whose fixed arity differs from a
|
|
853
|
-
# removed `Tuple`,
|
|
854
|
-
#
|
|
855
|
-
# `non-empty-
|
|
856
|
-
#
|
|
857
|
-
#
|
|
871
|
+
# removed `Tuple`, a `HashShape` with a required key a closed removed `HashShape` cannot hold, or a
|
|
872
|
+
# `Refined` whose predicate recogniser rejects the removed `Constant`. The range, tuple and shape
|
|
873
|
+
# arms cover the empty witnesses of `non-zero-int`, `non-empty-array[T]` and
|
|
874
|
+
# `non-empty-hash[K, V]`; the `Refined` arm is what lets `non-empty-string` accept
|
|
875
|
+
# `decimal-int-string`, which can never hold `""`. Any other carrier — a Nominal above all — is no
|
|
876
|
+
# proof and the difference rejects it under gradual mode, even where its values happen to exclude
|
|
877
|
+
# `removed`. A Union or Intersection argument never reaches this rule whole: `accepts` splits it
|
|
878
|
+
# into members first.
|
|
858
879
|
def accepts_difference(self_type, other_type, mode)
|
|
859
880
|
base_result = accepts(self_type.base, other_type, mode: mode)
|
|
860
881
|
return base_result if base_result.no?
|
|
@@ -884,16 +905,30 @@ module Rigor
|
|
|
884
905
|
# `removed`, the meet is too.
|
|
885
906
|
other_type.members.any? { |m| provably_disjoint_from_removed?(m, removed) }
|
|
886
907
|
when Type::IntegerRange
|
|
887
|
-
|
|
908
|
+
integer_range_excludes_removed?(other_type, removed)
|
|
888
909
|
when Type::Tuple
|
|
889
910
|
# A Tuple's arity is fixed, so it shares no inhabitant with a Tuple of another arity — the
|
|
890
911
|
# zero-arity witness `non-empty-array[T]` removes included.
|
|
891
912
|
removed.is_a?(Type::Tuple) && removed.elements.size != other_type.elements.size
|
|
892
913
|
when Type::HashShape
|
|
893
914
|
hash_shape_excludes_removed?(other_type, removed)
|
|
915
|
+
when Type::Refined
|
|
916
|
+
refined_excludes_removed?(other_type, removed)
|
|
894
917
|
end
|
|
895
918
|
end
|
|
896
919
|
|
|
920
|
+
def integer_range_excludes_removed?(range, removed)
|
|
921
|
+
removed.is_a?(Type::Constant) && removed.value.is_a?(Integer) && !range.covers?(removed.value)
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
# A predicate refinement excludes the removed value when its recogniser rejects the removed
|
|
925
|
+
# constant outright — `decimal-int-string` can never hold `""`. `matches?` answers nil for a
|
|
926
|
+
# predicate the registry does not know, and nil is no proof, so the comparison is `== false`
|
|
927
|
+
# rather than `!`.
|
|
928
|
+
def refined_excludes_removed?(refined, removed)
|
|
929
|
+
removed.is_a?(Type::Constant) && refined.matches?(removed.value) == false
|
|
930
|
+
end
|
|
931
|
+
|
|
897
932
|
# Every inhabitant of `shape` holds each of its required keys, and a closed `removed` shape holds no
|
|
898
933
|
# key outside its pairs, so one required key `removed` does not list makes the two disjoint. Against
|
|
899
934
|
# the closed `{}` witness `non-empty-hash[K, V]` removes, that is any required key; an optional key
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../source/node_children"
|
|
6
|
+
require_relative "../closure_escape_analyzer"
|
|
7
|
+
require_relative "../remover_holder_scan"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
module Inference
|
|
11
|
+
module AliasMutationFloor
|
|
12
|
+
# Issue #2335 — the *foreign* locals of a frame body ({AliasMutationFloor}): those whose object may have come
|
|
13
|
+
# from elsewhere, so they may be another local's object. A method parameter, a block or lambda parameter (but one
|
|
14
|
+
# of a catalogued iterator over literals that read nothing, whose parameters are those literals' own objects), a
|
|
15
|
+
# local any target node binds, and a local written anything but a new object.
|
|
16
|
+
module ForeignLocals
|
|
17
|
+
FRESH_VALUES = [
|
|
18
|
+
Prism::ArrayNode, Prism::HashNode, Prism::StringNode, Prism::InterpolatedStringNode, Prism::XStringNode,
|
|
19
|
+
Prism::InterpolatedXStringNode, Prism::IntegerNode, Prism::FloatNode, Prism::RationalNode, Prism::ImaginaryNode,
|
|
20
|
+
Prism::SymbolNode, Prism::InterpolatedSymbolNode, Prism::NilNode, Prism::TrueNode, Prism::FalseNode,
|
|
21
|
+
Prism::RangeNode, Prism::RegularExpressionNode, Prism::InterpolatedRegularExpressionNode
|
|
22
|
+
].freeze
|
|
23
|
+
# `+"x"`, `"x".dup`, `String.new`: a new String.
|
|
24
|
+
FRESH_STRING_CALLS = %i[+@ dup clone].to_set.freeze
|
|
25
|
+
LITERAL_PARTS = [Prism::ArrayNode, Prism::HashNode, Prism::KeywordHashNode, Prism::AssocNode,
|
|
26
|
+
*FRESH_VALUES].freeze
|
|
27
|
+
private_constant :FRESH_VALUES, :FRESH_STRING_CALLS, :LITERAL_PARTS
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# `[foreign, entry]`: the foreign locals, and the method parameters the body never rebinds, each frozen.
|
|
32
|
+
def collect(body, parameters)
|
|
33
|
+
foreign = Set.new
|
|
34
|
+
written = Set.new
|
|
35
|
+
collect_foreign(body, foreign, written)
|
|
36
|
+
finish(parameters, foreign, written)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# `[foreign, entry]` from the body's own `foreign` and `written` sets and the method's `parameters`.
|
|
40
|
+
def finish(parameters, foreign, written)
|
|
41
|
+
entry = Set.new
|
|
42
|
+
collect_parameters(parameters, entry)
|
|
43
|
+
[foreign.merge(entry).freeze, (entry - written).freeze]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def collect_parameters(node, names)
|
|
47
|
+
return if node.nil?
|
|
48
|
+
|
|
49
|
+
names << node.name if node.class.name.end_with?("ParameterNode") && node.respond_to?(:name) && node.name
|
|
50
|
+
node.rigor_each_child { |child| collect_parameters(child, names) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# `foreign` gains every block or lambda parameter and every local a write binds to an object from elsewhere (an
|
|
54
|
+
# operator write's value is a call result); `written` every local any write or target binds. A nested def is
|
|
55
|
+
# not entered.
|
|
56
|
+
def collect_foreign(node, foreign, written, literal_blocks = Set.new.compare_by_identity)
|
|
57
|
+
return if node.is_a?(Prism::DefNode)
|
|
58
|
+
|
|
59
|
+
visit(node, foreign, written, literal_blocks)
|
|
60
|
+
node.rigor_each_child { |child| collect_foreign(child, foreign, written, literal_blocks) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# {.collect_foreign}'s step for one node, which the site collector's walk also takes
|
|
64
|
+
# ({AliasMutationFloor::SiteCollector}).
|
|
65
|
+
def visit(node, foreign, written, literal_blocks)
|
|
66
|
+
case node
|
|
67
|
+
when Prism::CallNode then literal_blocks << node.block if literal_iterator_block?(node)
|
|
68
|
+
when Prism::BlockNode, Prism::LambdaNode
|
|
69
|
+
collect_parameters(node.parameters, foreign) unless literal_blocks.include?(node)
|
|
70
|
+
when Prism::LocalVariableTargetNode, Prism::LocalVariableOperatorWriteNode
|
|
71
|
+
foreign << written_name(node, written)
|
|
72
|
+
when Prism::LocalVariableWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode
|
|
73
|
+
name = written_name(node, written)
|
|
74
|
+
foreign << name unless fresh_value?(node.value)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# A catalogued iterator over literals that read nothing (`[[1], [2]].each { |x| }`,
|
|
79
|
+
# `[1].each_with_object({}) { |x, h| }`): its block's parameters are those literals' own new objects. Any other
|
|
80
|
+
# block's parameters may be any object.
|
|
81
|
+
def literal_iterator_block?(call_node)
|
|
82
|
+
return false unless call_node.block.is_a?(Prism::BlockNode)
|
|
83
|
+
return false unless ClosureEscapeAnalyzer.iterator_name?(call_node.name)
|
|
84
|
+
|
|
85
|
+
[call_node.receiver, *call_node.arguments&.arguments].all? do |operand|
|
|
86
|
+
!operand.nil? && literal_only?(operand)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def literal_only?(node)
|
|
91
|
+
return false unless LITERAL_PARTS.any? { |klass| node.is_a?(klass) }
|
|
92
|
+
|
|
93
|
+
node.rigor_each_child { |child| return false unless literal_only?(child) }
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def written_name(node, written)
|
|
98
|
+
written << node.name
|
|
99
|
+
node.name
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# A value that is a new object, so the local it binds holds no other holder's object.
|
|
103
|
+
def fresh_value?(value)
|
|
104
|
+
value = RemoverHolderScan.unparenthesised(value)
|
|
105
|
+
return true if FRESH_VALUES.any? { |klass| value.is_a?(klass) }
|
|
106
|
+
return false unless value.is_a?(Prism::CallNode)
|
|
107
|
+
return true if RemoverHolderScan.fresh_value?(value)
|
|
108
|
+
|
|
109
|
+
receiver = value.receiver
|
|
110
|
+
(FRESH_STRING_CALLS.include?(value.name) &&
|
|
111
|
+
(receiver.is_a?(Prism::StringNode) || receiver.is_a?(Prism::InterpolatedStringNode))) ||
|
|
112
|
+
(value.name == :new && receiver.is_a?(Prism::ConstantReadNode) && receiver.name == :String)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../typing_state"
|
|
4
|
+
require_relative "../../memo_audit"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module AliasMutationFloor
|
|
9
|
+
# ADR-129 WD1 — the floor's four run-scoped Memos, each a pure function of the key it holds, and their accessors.
|
|
10
|
+
# Each accessor's block computes the value: on a miss it is stored, on a hit {MemoAudit.check} compares it with
|
|
11
|
+
# the stored one under `RIGOR_MEMO_AUDIT=1`, and while an audit recomputation runs ({MemoAudit.suspended?}) it
|
|
12
|
+
# answers without reading or writing the memo.
|
|
13
|
+
module Memos
|
|
14
|
+
FACTS = TypingState.memo(
|
|
15
|
+
:alias_mutation_floor_facts,
|
|
16
|
+
run_scoped: true, audit: "alias floor facts",
|
|
17
|
+
reason: "keyed by the body node; the facts are a pure function of the body and its parameters"
|
|
18
|
+
)
|
|
19
|
+
IVAR = TypingState.memo(
|
|
20
|
+
:alias_mutation_floor_ivar_mutators,
|
|
21
|
+
run_scoped: true, audit: "alias floor ivar mutator",
|
|
22
|
+
reason: "keyed by def node; whether its body mutates an ivar is a pure function of the body"
|
|
23
|
+
)
|
|
24
|
+
FLOOR = TypingState.memo(
|
|
25
|
+
:alias_mutation_floor_callee_floors,
|
|
26
|
+
run_scoped: true, audit: "alias floor callee floor",
|
|
27
|
+
reason: "keyed by the literal shape; the callee block floor is a pure function of it"
|
|
28
|
+
)
|
|
29
|
+
STORE = TypingState.memo(
|
|
30
|
+
:alias_mutation_floor_unknown_stores,
|
|
31
|
+
run_scoped: true, audit: "alias floor unknown store",
|
|
32
|
+
reason: "keyed by call node, removed-keys mode and type, every input of the widened store"
|
|
33
|
+
)
|
|
34
|
+
private_constant :FACTS, :IVAR, :FLOOR, :STORE
|
|
35
|
+
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
# The facts of `body`. A hit is audited without the facts' per-read memos, which fill as reads are answered.
|
|
39
|
+
def facts(body, &)
|
|
40
|
+
return yield if MemoAudit.enabled? && MemoAudit.suspended?
|
|
41
|
+
|
|
42
|
+
table = TypingState.current.weak_key_map(FACTS)
|
|
43
|
+
facts = table[body]
|
|
44
|
+
return table[body] = MemoAudit.key_audit("alias floor facts", &) if facts.nil?
|
|
45
|
+
|
|
46
|
+
MemoAudit.check("alias floor facts", audited(facts), body) { audited(yield) } if MemoAudit.enabled?
|
|
47
|
+
facts
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def ivar_mutator(def_node, &)
|
|
51
|
+
return yield if MemoAudit.enabled? && MemoAudit.suspended?
|
|
52
|
+
|
|
53
|
+
table = TypingState.current.weak_key_map(IVAR)
|
|
54
|
+
cached = table[def_node]
|
|
55
|
+
return table[def_node] = MemoAudit.key_audit("alias floor ivar mutator", &) if cached.nil?
|
|
56
|
+
|
|
57
|
+
MemoAudit.check("alias floor ivar mutator", cached, def_node, &)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def callee_floor(type, &)
|
|
61
|
+
return yield if MemoAudit.enabled? && MemoAudit.suspended?
|
|
62
|
+
|
|
63
|
+
state = TypingState.current
|
|
64
|
+
table = (state[FLOOR] ||= {})
|
|
65
|
+
return table[type] = MemoAudit.key_audit("alias floor callee floor", &) unless table.key?(type)
|
|
66
|
+
|
|
67
|
+
MemoAudit.check("alias floor callee floor", table[type], type, &)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def unknown_store(call_node, removed_keys, type, &)
|
|
71
|
+
return yield if MemoAudit.enabled? && MemoAudit.suspended?
|
|
72
|
+
|
|
73
|
+
state = TypingState.current
|
|
74
|
+
table = (state[STORE] ||= {}.compare_by_identity)
|
|
75
|
+
per_mode = ((table[call_node] ||= {})[removed_keys] ||= {})
|
|
76
|
+
return per_mode[type] = MemoAudit.key_audit("alias floor unknown store", &) unless per_mode.key?(type)
|
|
77
|
+
|
|
78
|
+
MemoAudit.check("alias floor unknown store", per_mode[type], call_node, type, &)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def audited(facts) = facts.with(memo: nil, named_memo: nil)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|