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/scope.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
require_relative "type"
|
|
4
4
|
require_relative "environment"
|
|
5
5
|
require_relative "scope/discovery_index"
|
|
6
|
-
require_relative "
|
|
6
|
+
require_relative "typing_state"
|
|
7
|
+
require_relative "memo_audit"
|
|
7
8
|
require_relative "scope/resolution_chain"
|
|
8
9
|
require_relative "inference/definer_resolution"
|
|
9
10
|
require_relative "inference/in_effect_refinements"
|
|
@@ -17,6 +18,7 @@ require_relative "inference/def_node_resolver"
|
|
|
17
18
|
# Issue #2608 — read by `MutationWidening` at a remover call, which only an analysis makes; loaded with the engine
|
|
18
19
|
# so the engine-free value layer that requires `MutationWidening` stays as small as it was.
|
|
19
20
|
require_relative "inference/remover_narrowing"
|
|
21
|
+
require_relative "inference/alias_mutation_floor"
|
|
20
22
|
|
|
21
23
|
module Rigor
|
|
22
24
|
# Immutable analyzer scope: holds local-variable bindings and a reference to the surrounding Environment. State
|
|
@@ -157,7 +159,13 @@ module Rigor
|
|
|
157
159
|
# renames the foreign declaration. `published_constant_alias_names` holds the last segments of exactly
|
|
158
160
|
# those aliases, and matching on the last segment over-answers in the WITHHOLDING direction, which is the
|
|
159
161
|
# safe one.
|
|
162
|
+
#
|
|
163
|
+
# Issue #3199 — the answer turns on another file's publication of the last segment, so the question records
|
|
164
|
+
# the ADR-46 `constant:<segment>` name edge itself, whatever it answers. The guard asks it about spellings no
|
|
165
|
+
# typing step reads (a `defined?(B::X)` probe, a callee body the one hop scans), and an edge left to the read
|
|
166
|
+
# would let an `--incremental` run keep a verdict the moved publication no longer earns.
|
|
160
167
|
def published_constant?(name)
|
|
168
|
+
Analysis::DependencyRecorder.read_last_segment(:constant, name) if Analysis::DependencyRecorder.active?
|
|
161
169
|
names = @discovery.published_constant_names
|
|
162
170
|
return true if published_constant_alias?(name)
|
|
163
171
|
return false if names.empty?
|
|
@@ -287,6 +295,32 @@ module Rigor
|
|
|
287
295
|
:EMPTY_PUBLISHED_CONSTANT_IVARS, :EMPTY_REPEATED_OR_WRITES,
|
|
288
296
|
:EMPTY_CONSTANT_NARROWINGS, :EMPTY_GUARD_RECORDS, :EMPTY_BOT_GUARD_CLASSES
|
|
289
297
|
|
|
298
|
+
# ADR-129 WD1 — the four advisory tables are bound Tables: each is an identity Hash one file's scopes share by
|
|
299
|
+
# reference (a fresh body scope may start its own; {#with_isolated_side_tables} copies them), so a journal record
|
|
300
|
+
# names the store beside the node. Every entry read and write goes through the methods below, which log it while a
|
|
301
|
+
# capture is open; handing a store to a new Scope is not an entry read.
|
|
302
|
+
DYNAMIC_ORIGINS = TypingState.table(
|
|
303
|
+
:dynamic_origins,
|
|
304
|
+
bound: true,
|
|
305
|
+
reason: "node => DynamicOrigin cause; typing records it and reads it back for receiver provenance"
|
|
306
|
+
)
|
|
307
|
+
VOID_ORIGINS = TypingState.table(
|
|
308
|
+
:void_origins, bound: true, reason: "node => VoidOrigin site; recorded by dispatch, read by the value-use rule"
|
|
309
|
+
)
|
|
310
|
+
PLUGIN_TYPED_CALLS = TypingState.table(
|
|
311
|
+
:plugin_typed_calls, bound: true, reason: "node => true where a plugin's dynamic_return answered the call"
|
|
312
|
+
)
|
|
313
|
+
OPTIMISTIC_ORIGINS = TypingState.table(
|
|
314
|
+
:optimistic_origins,
|
|
315
|
+
bound: true,
|
|
316
|
+
reason: "node => OptimisticOrigin cause; typing records it and reads it back to keep a value nil-free"
|
|
317
|
+
)
|
|
318
|
+
private_constant :DYNAMIC_ORIGINS, :VOID_ORIGINS, :PLUGIN_TYPED_CALLS, :OPTIMISTIC_ORIGINS
|
|
319
|
+
TypingState.store_field(self, :@dynamic_origins, DYNAMIC_ORIGINS)
|
|
320
|
+
TypingState.store_field(self, :@void_origins, VOID_ORIGINS)
|
|
321
|
+
TypingState.store_field(self, :@plugin_typed_calls, PLUGIN_TYPED_CALLS)
|
|
322
|
+
TypingState.store_field(self, :@optimistic_origins, OPTIMISTIC_ORIGINS)
|
|
323
|
+
|
|
290
324
|
class << self
|
|
291
325
|
def empty(environment: Environment.default, source_path: nil)
|
|
292
326
|
new(environment: environment, locals: {}.freeze,
|
|
@@ -295,16 +329,33 @@ module Rigor
|
|
|
295
329
|
end
|
|
296
330
|
|
|
297
331
|
def record_dynamic_origin(node, cause)
|
|
332
|
+
TypingState.current.log_bound_write(DYNAMIC_ORIGINS, @dynamic_origins, node, cause)
|
|
298
333
|
@dynamic_origins[node] = cause
|
|
299
334
|
self
|
|
300
335
|
end
|
|
301
336
|
|
|
337
|
+
# The {Inference::DynamicOrigin} cause recorded at `node`, or nil (ADR-129: a logged Table read).
|
|
338
|
+
def dynamic_origin(node)
|
|
339
|
+
log_advisory_read(DYNAMIC_ORIGINS, @dynamic_origins, node)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# The {Inference::VoidOrigin} recorded at `node`, or nil (ADR-129: a logged Table read).
|
|
343
|
+
def void_origin(node)
|
|
344
|
+
log_advisory_read(VOID_ORIGINS, @void_origins, node)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# The {Inference::OptimisticOrigin} cause recorded at `node`, or nil (ADR-129: a logged Table read).
|
|
348
|
+
def optimistic_origin(node)
|
|
349
|
+
log_advisory_read(OPTIMISTIC_ORIGINS, @optimistic_origins, node)
|
|
350
|
+
end
|
|
351
|
+
|
|
302
352
|
# ADR-100 WD3 — records that the value introduced at `node` (a call node) is a `top` recovered from an
|
|
303
353
|
# author-declared `-> void` return, keyed by the origin site (`origin`, an {Inference::VoidOrigin}). The
|
|
304
354
|
# value-context check rule `static.value-use.void` consumes this table. Mirrors {#record_dynamic_origin}
|
|
305
355
|
# exactly: identity-keyed advisory metadata, mutated in place on the shared table (threaded by reference
|
|
306
356
|
# through `#join` / `#rebuild`), excluded from `==` / `hash`, so it never forks a flow-dedup or cache key.
|
|
307
357
|
def record_void_origin(node, origin)
|
|
358
|
+
TypingState.current.log_bound_write(VOID_ORIGINS, @void_origins, node, origin)
|
|
308
359
|
@void_origins[node] = origin
|
|
309
360
|
self
|
|
310
361
|
end
|
|
@@ -317,17 +368,22 @@ module Rigor
|
|
|
317
368
|
# identity-keyed advisory metadata on a table threaded by reference through `#join` / `#rebuild`,
|
|
318
369
|
# excluded from `==` / `hash`, so it never forks a flow-dedup or cache key.
|
|
319
370
|
def record_plugin_typed_call(node)
|
|
371
|
+
TypingState.current.log_bound_write(PLUGIN_TYPED_CALLS, @plugin_typed_calls, node, true)
|
|
320
372
|
@plugin_typed_calls[node] = true
|
|
321
373
|
self
|
|
322
374
|
end
|
|
323
375
|
|
|
324
376
|
# Issue #653 — whether a plugin answered the return type for `node` on this run.
|
|
325
377
|
def plugin_typed_call?(node)
|
|
326
|
-
@plugin_typed_calls.
|
|
378
|
+
!log_advisory_read(PLUGIN_TYPED_CALLS, @plugin_typed_calls, node).nil?
|
|
327
379
|
end
|
|
328
380
|
|
|
329
|
-
# Every keyword past the 31st
|
|
330
|
-
# default
|
|
381
|
+
# Every keyword past the 31st (`opaque_block_self`, `block_self_unknown`) must keep a literal default. A keyword at
|
|
382
|
+
# that index with a non-literal default makes a call that omits it allocate CRuby's unspecified-keywords record
|
|
383
|
+
# during argument setup, and on CRuby 4.0 a GC started by that allocation frees the values already moved into the
|
|
384
|
+
# keyword locals (ruby/ruby Bug #22500, issue #2850): `Scope.new` stored a freed `match_frame`.
|
|
385
|
+
# `spec/rigor/keyword_gc_safety_spec.rb` holds every method under `lib/` and `plugins/*/lib` to this. See the note
|
|
386
|
+
# at {#rebuild} before adding a field.
|
|
331
387
|
def initialize( # rubocop:disable Metrics/AbcSize, Metrics/MethodLength -- one keyword and assignment per field
|
|
332
388
|
environment:, locals:,
|
|
333
389
|
fact_store: Analysis::FactStore.empty,
|
|
@@ -342,8 +398,6 @@ module Rigor
|
|
|
342
398
|
published_constant_sourced: EMPTY_PUBLISHED_CONSTANT_SOURCED,
|
|
343
399
|
source_path: nil,
|
|
344
400
|
struct_fold_safe_locals: EMPTY_FOLD_SAFE,
|
|
345
|
-
opaque_block_self: false,
|
|
346
|
-
block_self_unknown: false,
|
|
347
401
|
singleton_class_body: false,
|
|
348
402
|
lexical_nesting: nil,
|
|
349
403
|
dynamic_origins: {}.compare_by_identity,
|
|
@@ -360,7 +414,10 @@ module Rigor
|
|
|
360
414
|
constant_narrowings: EMPTY_CONSTANT_NARROWINGS,
|
|
361
415
|
guard_records: EMPTY_GUARD_RECORDS,
|
|
362
416
|
bot_guard_classes: EMPTY_BOT_GUARD_CLASSES,
|
|
363
|
-
declared_refinements: Inference::InEffectRefinements::EMPTY
|
|
417
|
+
declared_refinements: Inference::InEffectRefinements::EMPTY,
|
|
418
|
+
# Literal defaults only from here on; see the note above.
|
|
419
|
+
opaque_block_self: false,
|
|
420
|
+
block_self_unknown: false
|
|
364
421
|
)
|
|
365
422
|
@environment = environment
|
|
366
423
|
@locals = locals
|
|
@@ -403,10 +460,20 @@ module Rigor
|
|
|
403
460
|
# nil-freeness is a property of its class. Mirrors {#dynamic_origins} / {#void_origins}: advisory
|
|
404
461
|
# metadata, ignored by `==` / `hash`, and never varying a flow decision on its own.
|
|
405
462
|
def record_optimistic_origin(node, cause)
|
|
463
|
+
TypingState.current.log_bound_write(OPTIMISTIC_ORIGINS, @optimistic_origins, node, cause)
|
|
406
464
|
@optimistic_origins[node] = cause
|
|
407
465
|
self
|
|
408
466
|
end
|
|
409
467
|
|
|
468
|
+
# The entry of the advisory table `store` (the bound Table `slot`'s) at `node`, or nil, logged as a Table read: a
|
|
469
|
+
# key the store does not hold logs {TypingState::ABSENT}, so a recorded nil and a miss differ.
|
|
470
|
+
def log_advisory_read(slot, store, node)
|
|
471
|
+
value = store.fetch(node, TypingState::ABSENT)
|
|
472
|
+
TypingState.current.log_bound_read(slot, store, node, value)
|
|
473
|
+
TypingState::ABSENT.equal?(value) ? nil : value
|
|
474
|
+
end
|
|
475
|
+
private :log_advisory_read
|
|
476
|
+
|
|
410
477
|
# Issue #1703 — this scope with private copies of the identity-keyed side tables (dynamic, void and optimistic
|
|
411
478
|
# origins, plugin-typed calls), so a second walk from it — the `key?`-guarded re-walk — records into its own
|
|
412
479
|
# tables and never into the ones the file's analysis reads.
|
|
@@ -639,9 +706,36 @@ module Rigor
|
|
|
639
706
|
rebuild(opaque_block_self: true, block_self_unknown: true)
|
|
640
707
|
end
|
|
641
708
|
|
|
709
|
+
# Issue #2861 — the {#block_self_unknown?} value a block whose yielding call provably runs it under the
|
|
710
|
+
# caller's own `self` ({Inference::SelfPreservingBlock}) is entered with, when the caller's `self` is not
|
|
711
|
+
# already unmodelled. It reads as unknown everywhere `true` does, so issue #1717's explicit-`self` exemption is
|
|
712
|
+
# unchanged; what it withholds is the #316 opaque mark, and it tells a block nested beneath it that it does
|
|
713
|
+
# not sit under an index walk ({#walked_block_self_unknown?}).
|
|
714
|
+
PRESERVED_BLOCK_SELF = :preserved
|
|
715
|
+
|
|
716
|
+
# Issue #2861 — the entry for a block {Inference::SelfPreservingBlock} proves runs under the caller's `self`
|
|
717
|
+
# (`rows.map { … }`, `5.times { … }`). Ruby gives the block the `self` the call site has, so the block is as
|
|
718
|
+
# opaque as the scope around it and no more: it keeps whatever #316 mark the caller's scope carries and adds
|
|
719
|
+
# none, so a cross-file top-level `def` binds there exactly where it binds beside the call
|
|
720
|
+
# ({#bindable_top_level_def_for}). A caller's unknown-`self` mark of `true` or {PRESERVED_BLOCK_SELF} is kept as
|
|
721
|
+
# it is (no rebuild); a {REFINEMENT_BLOCK_SELF} one is not, since the block's `self` is not the refinement module
|
|
722
|
+
# the mark describes.
|
|
723
|
+
def entering_self_preserving_block
|
|
724
|
+
return self if @block_self_unknown == true || @block_self_unknown == PRESERVED_BLOCK_SELF
|
|
725
|
+
|
|
726
|
+
rebuild(block_self_unknown: PRESERVED_BLOCK_SELF)
|
|
727
|
+
end
|
|
728
|
+
|
|
642
729
|
# True when this scope sits inside a block whose `self` is unmodelled ({#entering_opaque_block}).
|
|
643
730
|
def opaque_block_self? = @opaque_block_self
|
|
644
731
|
|
|
732
|
+
# Issue #2861 — true where the unknown-`self` mark came from the index walk or a value-position block
|
|
733
|
+
# ({#with_block_self_unknown}) rather than a block entry: the mark is set, the #316 one is not, and it is not
|
|
734
|
+
# a {PRESERVED_BLOCK_SELF} entry's. A block nested in such a scope keeps the #316 mark off (issue #1717).
|
|
735
|
+
def walked_block_self_unknown?
|
|
736
|
+
!!@block_self_unknown && !@opaque_block_self && @block_self_unknown != PRESERVED_BLOCK_SELF
|
|
737
|
+
end
|
|
738
|
+
|
|
645
739
|
# Issue #1717 — true when this scope sits in a block whose `self` the engine does not know, so an explicit
|
|
646
740
|
# `self.m` there is no evidence `m` is missing: the yielding method may `instance_exec` the block on another
|
|
647
741
|
# object. Read ONLY by `call.undefined-method`'s explicit-`self` exemption, and kept apart from
|
|
@@ -897,6 +991,56 @@ module Rigor
|
|
|
897
991
|
@declaration_sourced.include?([:inferred_param, name.to_sym])
|
|
898
992
|
end
|
|
899
993
|
|
|
994
|
+
# Issue #2656 — stamp the gradual-narrowing mark on the local `name`: a class guard narrowed it from a `Dynamic`
|
|
995
|
+
# value, so its `Nominal` reading is the guard's, not a contract the value was checked against. Rides the ADR-58
|
|
996
|
+
# side-mark machinery under a distinct `:gradual_narrowing` kind, as `:inferred_param` does: the displayed type
|
|
997
|
+
# stays the narrowed `Nominal` (dispatch, `call.undefined-method` and `call.wrong-arity` read it), and only
|
|
998
|
+
# `call.argument-type-mismatch` consults the mark ({Analysis::CheckRules::DeclarationSourcedGuard.gradual_narrowed?}).
|
|
999
|
+
# Like the `:local` mark it is dropped by `with_local` and intersected by `join`. Applied after the binding.
|
|
1000
|
+
def with_gradual_narrowing_mark(name)
|
|
1001
|
+
marks = add_declaration_sourced(:gradual_narrowing, name)
|
|
1002
|
+
return self if marks.equal?(@declaration_sourced)
|
|
1003
|
+
|
|
1004
|
+
marks = (marks.dup << GRADUAL_HINT).freeze unless marks.include?(GRADUAL_HINT)
|
|
1005
|
+
rebuild(declaration_sourced: marks)
|
|
1006
|
+
end
|
|
1007
|
+
|
|
1008
|
+
# Issue #2656 — a ref stamped beside every gradual-narrowing mark, so {#gradual_narrowing_names} can answer "none"
|
|
1009
|
+
# with one hash lookup instead of a walk of the set, which the ADR-67 marks keep non-empty in most bodies. It is a
|
|
1010
|
+
# hint: a drop leaves it, and the walk then finds no mark; a join keeps it only where both arms carry it.
|
|
1011
|
+
GRADUAL_HINT = [:gradual_narrowing_hint].freeze
|
|
1012
|
+
private_constant :GRADUAL_HINT
|
|
1013
|
+
|
|
1014
|
+
# Issue #2656 — this scope without the gradual-narrowing mark of every local `block_node` writes: a block may run
|
|
1015
|
+
# again after its own write (`loop do mode(x); x = "zz".dup; end`), and its entry does not see that back edge, so
|
|
1016
|
+
# a mark from before the block must not reach a read the write may precede. Zero-alloc when no local is marked.
|
|
1017
|
+
def entering_block_without_gradual_marks(block_node)
|
|
1018
|
+
names = gradual_narrowing_names
|
|
1019
|
+
return self if names.empty?
|
|
1020
|
+
|
|
1021
|
+
written = Inference::OperandEffects.written_variables(block_node).map(&:to_sym)
|
|
1022
|
+
dropped = names.reduce(@declaration_sourced) do |marks, name|
|
|
1023
|
+
written.include?(name) ? drop_gradual_narrowing_mark(marks, name) : marks
|
|
1024
|
+
end
|
|
1025
|
+
dropped.equal?(@declaration_sourced) ? self : rebuild(declaration_sourced: dropped)
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
# Issue #2656 — the locals carrying the gradual-narrowing mark, empty when none does.
|
|
1029
|
+
def gradual_narrowing_names
|
|
1030
|
+
return EMPTY_GRADUAL_NAMES unless @declaration_sourced.include?(GRADUAL_HINT)
|
|
1031
|
+
|
|
1032
|
+
@declaration_sourced.filter_map { |ref| ref[1] if ref[0] == :gradual_narrowing }
|
|
1033
|
+
end
|
|
1034
|
+
EMPTY_GRADUAL_NAMES = [].freeze
|
|
1035
|
+
private_constant :EMPTY_GRADUAL_NAMES
|
|
1036
|
+
|
|
1037
|
+
# Issue #2656 — true when the local `name`'s binding is a class guard's narrowing of a `Dynamic` value.
|
|
1038
|
+
def gradual_narrowing?(name)
|
|
1039
|
+
return false if @declaration_sourced.empty?
|
|
1040
|
+
|
|
1041
|
+
@declaration_sourced.include?([:gradual_narrowing, name.to_sym])
|
|
1042
|
+
end
|
|
1043
|
+
|
|
900
1044
|
def with_cvar(name, type)
|
|
901
1045
|
rebuild(cvars: @cvars.merge(name.to_sym => type).freeze)
|
|
902
1046
|
end
|
|
@@ -1280,12 +1424,33 @@ module Rigor
|
|
|
1280
1424
|
# a file that starts or stops defining it re-analyses the reader (issue #1459's `defines:` edge).
|
|
1281
1425
|
def project_method_name?(method_name)
|
|
1282
1426
|
Analysis::DependencyRecorder.read_name(:defines, method_name) if Analysis::DependencyRecorder.active?
|
|
1283
|
-
|
|
1284
|
-
return true if (table[@discovery] ||= collect_project_method_names).include?(method_name.to_sym)
|
|
1427
|
+
return true if project_method_names.include?(method_name.to_sym)
|
|
1285
1428
|
|
|
1286
1429
|
false
|
|
1287
1430
|
end
|
|
1288
1431
|
|
|
1432
|
+
# The name set {#project_method_name?} reads, held per discovery index. ADR-129 G2: a hit is checked against a
|
|
1433
|
+
# fresh collection under `RIGOR_MEMO_AUDIT=1`, and an audit recomputation bypasses the table.
|
|
1434
|
+
def project_method_names
|
|
1435
|
+
return collect_project_method_names if memo_audit_bypass?
|
|
1436
|
+
|
|
1437
|
+
table = TypingState.current.weak_key_map(PROJECT_METHOD_NAMES)
|
|
1438
|
+
names = table[@discovery]
|
|
1439
|
+
if names.nil?
|
|
1440
|
+
return table[@discovery] = Inference::MemoAudit.key_audit("project method names") do
|
|
1441
|
+
collect_project_method_names
|
|
1442
|
+
end
|
|
1443
|
+
end
|
|
1444
|
+
|
|
1445
|
+
Inference::MemoAudit.check("project method names", names, :discovery) { collect_project_method_names }
|
|
1446
|
+
end
|
|
1447
|
+
private :project_method_names
|
|
1448
|
+
|
|
1449
|
+
# ADR-129 G2 — true while a MemoAudit recomputation runs: the memos wired here are then bypassed, neither read
|
|
1450
|
+
# nor written. One boolean check when the audit is off.
|
|
1451
|
+
def memo_audit_bypass? = Inference::MemoAudit.enabled? && Inference::MemoAudit.suspended?
|
|
1452
|
+
private :memo_audit_bypass?
|
|
1453
|
+
|
|
1289
1454
|
def collect_project_method_names
|
|
1290
1455
|
names = Set.new
|
|
1291
1456
|
[@discovery.discovered_methods, @discovery.discovered_def_nodes, @discovery.discovered_singleton_def_nodes,
|
|
@@ -1296,9 +1461,13 @@ module Rigor
|
|
|
1296
1461
|
end
|
|
1297
1462
|
private :collect_project_method_names
|
|
1298
1463
|
|
|
1299
|
-
#
|
|
1300
|
-
|
|
1301
|
-
|
|
1464
|
+
# ADR-129 WD1 — a run-scoped Memo ({TypingState}): emptied when a run ends, so an edited project is read afresh.
|
|
1465
|
+
PROJECT_METHOD_NAMES = TypingState.memo(
|
|
1466
|
+
:project_method_names,
|
|
1467
|
+
run_scoped: true, audit: "project method names",
|
|
1468
|
+
reason: "a WeakKeyMap keyed on the discovery index; the name set reads only its tables"
|
|
1469
|
+
)
|
|
1470
|
+
private_constant :PROJECT_METHOD_NAMES
|
|
1302
1471
|
|
|
1303
1472
|
def record_discovered_method(class_name, method_name)
|
|
1304
1473
|
record_class_dependency(class_name)
|
|
@@ -1352,6 +1521,9 @@ module Rigor
|
|
|
1352
1521
|
def user_instance_def_owners(method_name)
|
|
1353
1522
|
Analysis::DependencyRecorder.read_name(:defines, method_name) if Analysis::DependencyRecorder.active?
|
|
1354
1523
|
name = method_name.to_sym
|
|
1524
|
+
# `each` with a lazily built array, not `filter_map`: this runs once per call on every discovered class, and
|
|
1525
|
+
# `Hash#filter_map` allocates a `[key, value]` pair per entry (#2895 cost 3.35% of the release gate's
|
|
1526
|
+
# allocations that way).
|
|
1355
1527
|
owners = nil
|
|
1356
1528
|
@discovery.discovered_def_nodes.each do |class_name, defs|
|
|
1357
1529
|
next unless defs.key?(name) && class_name != Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY
|
|
@@ -1540,18 +1712,31 @@ module Rigor
|
|
|
1540
1712
|
# call's literal name and a def in a block. Held by the table's identity, as {#toplevel_definition_rows} is.
|
|
1541
1713
|
def deferred_definition_names
|
|
1542
1714
|
ranges = @discovery.discovered_deferred_ranges
|
|
1543
|
-
|
|
1715
|
+
return collect_deferred_definition_names(ranges) if memo_audit_bypass?
|
|
1716
|
+
|
|
1717
|
+
state = TypingState.current
|
|
1718
|
+
slots = (state[DEFERRED_NAMES] ||= [])
|
|
1544
1719
|
slot = slots.find { |entry| entry[0].equal?(ranges) }
|
|
1545
|
-
|
|
1720
|
+
if slot
|
|
1721
|
+
return Inference::MemoAudit.check("deferred definition names", slot[1], :ranges) do
|
|
1722
|
+
collect_deferred_definition_names(ranges)
|
|
1723
|
+
end
|
|
1724
|
+
end
|
|
1546
1725
|
|
|
1726
|
+
names = Inference::MemoAudit.key_audit("deferred definition names") { collect_deferred_definition_names(ranges) }
|
|
1727
|
+
slots.unshift([ranges, names]).slice!(2..)
|
|
1728
|
+
names
|
|
1729
|
+
end
|
|
1730
|
+
private :deferred_definition_names
|
|
1731
|
+
|
|
1732
|
+
def collect_deferred_definition_names(ranges)
|
|
1547
1733
|
names = Set.new
|
|
1548
1734
|
ranges.each_value do |entries|
|
|
1549
1735
|
entries.each { |(_start, _finish, name, kind, owner)| names << name if name && kind.nil? && owner.nil? }
|
|
1550
1736
|
end
|
|
1551
|
-
|
|
1552
|
-
names
|
|
1737
|
+
names.freeze
|
|
1553
1738
|
end
|
|
1554
|
-
private :
|
|
1739
|
+
private :collect_deferred_definition_names
|
|
1555
1740
|
|
|
1556
1741
|
# Issue #2336 — {#module_function_def?}'s `:both` reading for a def in any file: whether `def_node`, an instance
|
|
1557
1742
|
# def of `owner`, is a module function by a deferred-range row over it (`module_function def x`, or a bare
|
|
@@ -1571,10 +1756,24 @@ module Rigor
|
|
|
1571
1756
|
# a singleton row. Held by the table's identity (a file's merged copy and the seed), released with the run.
|
|
1572
1757
|
def toplevel_definition_rows
|
|
1573
1758
|
ranges = @discovery.discovered_deferred_ranges
|
|
1574
|
-
|
|
1759
|
+
return collect_toplevel_definition_rows(ranges) if memo_audit_bypass?
|
|
1760
|
+
|
|
1761
|
+
state = TypingState.current
|
|
1762
|
+
slots = (state[TOPLEVEL_ROWS] ||= [])
|
|
1575
1763
|
slot = slots.find { |entry| entry[0].equal?(ranges) }
|
|
1576
|
-
|
|
1764
|
+
if slot
|
|
1765
|
+
return Inference::MemoAudit.check("toplevel definition rows", slot[1], :ranges) do
|
|
1766
|
+
collect_toplevel_definition_rows(ranges)
|
|
1767
|
+
end
|
|
1768
|
+
end
|
|
1577
1769
|
|
|
1770
|
+
rows = Inference::MemoAudit.key_audit("toplevel definition rows") { collect_toplevel_definition_rows(ranges) }
|
|
1771
|
+
slots.unshift([ranges, rows]).slice!(2..)
|
|
1772
|
+
rows
|
|
1773
|
+
end
|
|
1774
|
+
private :toplevel_definition_rows
|
|
1775
|
+
|
|
1776
|
+
def collect_toplevel_definition_rows(ranges)
|
|
1578
1777
|
rows = Hash.new(0)
|
|
1579
1778
|
ranges.each_value do |entries|
|
|
1580
1779
|
entries.each do |(_start, _finish, name, kind, owner)|
|
|
@@ -1583,30 +1782,55 @@ module Rigor
|
|
|
1583
1782
|
rows[name] = kind == :singleton || rows[name] == MAIN_SINGLETON_ROWS ? MAIN_SINGLETON_ROWS : rows[name] + 1
|
|
1584
1783
|
end
|
|
1585
1784
|
end
|
|
1586
|
-
|
|
1587
|
-
rows
|
|
1785
|
+
rows.freeze
|
|
1588
1786
|
end
|
|
1589
|
-
private :
|
|
1787
|
+
private :collect_toplevel_definition_rows
|
|
1590
1788
|
|
|
1591
1789
|
# The names an `alias` statement or `alias_method` call introduces, from the global write census; held by the
|
|
1592
1790
|
# census's identity as {#toplevel_definition_rows} is.
|
|
1593
1791
|
def method_alias_names
|
|
1594
1792
|
census = @discovery.discovered_global_write_census
|
|
1595
|
-
|
|
1793
|
+
return collect_method_alias_names(census) if memo_audit_bypass?
|
|
1794
|
+
|
|
1795
|
+
state = TypingState.current
|
|
1796
|
+
slots = (state[METHOD_ALIASES] ||= [])
|
|
1596
1797
|
slot = slots.find { |entry| entry[0].equal?(census) }
|
|
1597
|
-
|
|
1798
|
+
if slot
|
|
1799
|
+
return Inference::MemoAudit.check("method alias names", slot[1], :census) { collect_method_alias_names(census) }
|
|
1800
|
+
end
|
|
1598
1801
|
|
|
1599
|
-
names =
|
|
1802
|
+
names = Inference::MemoAudit.key_audit("method alias names") { collect_method_alias_names(census) }
|
|
1600
1803
|
slots.unshift([census, names]).slice!(2..)
|
|
1601
1804
|
names
|
|
1602
1805
|
end
|
|
1603
1806
|
private :method_alias_names
|
|
1604
1807
|
|
|
1808
|
+
def collect_method_alias_names(census)
|
|
1809
|
+
census.each_with_object(Set.new) { |entry, acc| acc << entry[1] if entry[0] == :method_alias }.freeze
|
|
1810
|
+
end
|
|
1811
|
+
private :collect_method_alias_names
|
|
1812
|
+
|
|
1605
1813
|
MAIN_SINGLETON_ROWS = -1
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1814
|
+
# ADR-129 WD1 — three run-scoped Memos, each a two-entry list of `[table, derived]` held by the identity of the
|
|
1815
|
+
# table the derived value reads (the deferred ranges, or the global write census).
|
|
1816
|
+
TOPLEVEL_ROWS = TypingState.memo(
|
|
1817
|
+
:toplevel_definition_rows,
|
|
1818
|
+
run_scoped: true, audit: "toplevel definition rows",
|
|
1819
|
+
reason: "[deferred ranges, rows] entries keyed on the identity of the ranges table, the only input the rows read"
|
|
1820
|
+
)
|
|
1821
|
+
METHOD_ALIASES = TypingState.memo(
|
|
1822
|
+
:method_alias_names,
|
|
1823
|
+
run_scoped: true, audit: "method alias names",
|
|
1824
|
+
reason: "[census, names] entries keyed on the identity of the global write census, the only input the names " \
|
|
1825
|
+
"read"
|
|
1826
|
+
)
|
|
1827
|
+
DEFERRED_NAMES = TypingState.memo(
|
|
1828
|
+
:deferred_definition_names,
|
|
1829
|
+
run_scoped: true, audit: "deferred definition names",
|
|
1830
|
+
reason: "[deferred ranges, names] entries keyed on the identity of the ranges table, the only input the names " \
|
|
1831
|
+
"read"
|
|
1832
|
+
)
|
|
1833
|
+
private_constant :MAIN_SINGLETON_ROWS, :TOPLEVEL_ROWS, :METHOD_ALIASES, :DEFERRED_NAMES
|
|
1610
1834
|
|
|
1611
1835
|
# Issue #316 — the CONFIDENCE-GATED companion of {#top_level_def_for}, and the only accessor the type
|
|
1612
1836
|
# inference may bind through. {#top_level_def_for} stays unrestricted because it also serves the
|
|
@@ -1847,6 +2071,14 @@ module Rigor
|
|
|
1847
2071
|
@discovery.discovered_includes[class_name.to_s] || []
|
|
1848
2072
|
end
|
|
1849
2073
|
|
|
2074
|
+
# Issue #2986 — the modules `class_name` prepends, as written, in search order: the prepended part of
|
|
2075
|
+
# {#includes_of}, which lists them ahead of the included ones. An ActiveSupport::Concern's `prepended do … end`
|
|
2076
|
+
# block runs for a prepend edge only, and its `included do … end` block for an include edge only.
|
|
2077
|
+
def prepends_of(class_name)
|
|
2078
|
+
record_class_dependency(class_name) if Analysis::DependencyRecorder.active?
|
|
2079
|
+
@discovery.discovered_prepends[class_name.to_s] || EMPTY_PREPENDS
|
|
2080
|
+
end
|
|
2081
|
+
|
|
1850
2082
|
# Issue #898 — the module names `extend`ed onto `class_name`'s SINGLETON, as written, gathered up the
|
|
1851
2083
|
# as-written superclass chain because a singleton class inherits its superclass's singleton class
|
|
1852
2084
|
# (`class Base; extend Comparable; end; class Widget < Base; end` leaves `Widget.is_a?(Comparable)`
|
|
@@ -1913,6 +2145,7 @@ module Rigor
|
|
|
1913
2145
|
end
|
|
1914
2146
|
return found if found
|
|
1915
2147
|
|
|
2148
|
+
chain.record_external_absence
|
|
1916
2149
|
chain.truncated? ? ancestor_walk_gave_up : [nil, nil]
|
|
1917
2150
|
end
|
|
1918
2151
|
|
|
@@ -1952,6 +2185,7 @@ module Rigor
|
|
|
1952
2185
|
end
|
|
1953
2186
|
return found if found
|
|
1954
2187
|
|
|
2188
|
+
chain.record_external_absence
|
|
1955
2189
|
chain.truncated? ? ancestor_walk_gave_up : [nil, nil]
|
|
1956
2190
|
end
|
|
1957
2191
|
|
|
@@ -2035,6 +2269,8 @@ module Rigor
|
|
|
2035
2269
|
discovered_method?(entry.name, method_name, entry.side == :singleton ? :singleton : kind_for(kind))
|
|
2036
2270
|
end
|
|
2037
2271
|
return true if found
|
|
2272
|
+
|
|
2273
|
+
record_discovered_miss(chain, method_name) if Analysis::DependencyRecorder.active?
|
|
2038
2274
|
# Budget exhaustion is uncertainty, not absence: answering "not declared" here would hand a
|
|
2039
2275
|
# `call.undefined-method` a fired verdict it has no evidence for. Suppress and record the hit.
|
|
2040
2276
|
return false unless chain.truncated?
|
|
@@ -2043,6 +2279,28 @@ module Rigor
|
|
|
2043
2279
|
true
|
|
2044
2280
|
end
|
|
2045
2281
|
|
|
2282
|
+
# Issue #2689 — the ADR-46 negative edges of a discovery-walk miss: the answer is "no project entry on the chain
|
|
2283
|
+
# defines it", so a later edit that defines it on any of them, or a new file that reopens one of them (and may
|
|
2284
|
+
# change the chain, `include` a module that defines it), turns it. Each project entry files the `method:` key
|
|
2285
|
+
# `Incremental.appeared_symbols` emits for that definition (`Name.method` for a class object's own side,
|
|
2286
|
+
# `Name#method` for an instance-chain entry and for a module on the singleton chain, which answers with its
|
|
2287
|
+
# instance methods) and the `class:` key a declaring file appearing fires — the edges the def walk files for the
|
|
2288
|
+
# same miss (`#user_def_for` per entry, `ResolutionChain#settle` for the classes). A hit files none: existence is
|
|
2289
|
+
# a union, so no other entry gaining the method can turn a found answer.
|
|
2290
|
+
# An external entry (an ancestor declared with no methods, or not yet) files its `class:` key
|
|
2291
|
+
# ({ResolutionChain#record_external_absence}), as the def walks do on a miss.
|
|
2292
|
+
def record_discovered_miss(chain, method_name)
|
|
2293
|
+
chain.record_external_absence
|
|
2294
|
+
chain.entries.each do |entry|
|
|
2295
|
+
next if entry.external?
|
|
2296
|
+
|
|
2297
|
+
separator = entry.side == :singleton ? "." : "#"
|
|
2298
|
+
Analysis::DependencyRecorder.read_missing(:method, "#{entry.name}#{separator}#{method_name}")
|
|
2299
|
+
Analysis::DependencyRecorder.read_missing(:class, entry.last_segment)
|
|
2300
|
+
end
|
|
2301
|
+
end
|
|
2302
|
+
private :record_discovered_miss
|
|
2303
|
+
|
|
2046
2304
|
# A module entry on the singleton chain answers with its INSTANCE methods; on the instance chain, the
|
|
2047
2305
|
# caller's kind stands.
|
|
2048
2306
|
def kind_for(kind) = kind == :singleton ? :instance : kind
|
|
@@ -2367,8 +2625,9 @@ module Rigor
|
|
|
2367
2625
|
# against THIS scope's discovery index (so cross-file dispatches in the body resolve). The incremental
|
|
2368
2626
|
# session re-drives a declaration-stable changed callee at each previously-observed call key to prove its
|
|
2369
2627
|
# return is unchanged before skipping its symbol dependents.
|
|
2370
|
-
def user_method_return(def_node, receiver, arg_types)
|
|
2371
|
-
Inference::ExpressionTyper.new(scope: self)
|
|
2628
|
+
def user_method_return(def_node, receiver, arg_types, positionals_unknown: false)
|
|
2629
|
+
typer = Inference::ExpressionTyper.new(scope: self)
|
|
2630
|
+
typer.return_type_for(def_node, receiver, arg_types, positionals_unknown: positionals_unknown)
|
|
2372
2631
|
end
|
|
2373
2632
|
|
|
2374
2633
|
# Statement-level evaluation: returns the pair `[type, scope']` where `type` is what the node produces and
|
|
@@ -2465,9 +2724,10 @@ module Rigor
|
|
|
2465
2724
|
# field indexed by each keyword's position among ALL the method's keywords, required and optional
|
|
2466
2725
|
# (`VM_KW_SPECIFIED_BITS_MAX`, 31); a keyword past that index whose default is not a literal makes every call that
|
|
2467
2726
|
# omits it allocate the record instead. A 32nd keyword here did that on every scope transition, about 1% of an
|
|
2468
|
-
# analysis's allocations (issue #2555).
|
|
2469
|
-
#
|
|
2470
|
-
#
|
|
2727
|
+
# analysis's allocations (issue #2555). On CRuby 4.0 that allocation is also unsafe: a GC it starts frees the
|
|
2728
|
+
# values already moved into the keyword locals (ruby/ruby Bug #22500, issue #2850). `#initialize` is past the index
|
|
2729
|
+
# and keeps literal defaults there for that reason. The next Scope field crosses it in this method again, so it
|
|
2730
|
+
# needs a positional slot or a restructuring, not a 32nd keyword.
|
|
2471
2731
|
def rebuild(
|
|
2472
2732
|
construction_facts = @construction_facts,
|
|
2473
2733
|
locals: @locals, fact_store: @fact_store, self_type: @self_type,
|
|
@@ -2892,6 +3152,7 @@ module Rigor
|
|
|
2892
3152
|
# beside it ({#with_global_copy_marks}).
|
|
2893
3153
|
def drop_local_declaration_marks(name)
|
|
2894
3154
|
dropped = drop_declaration_sourced_for(:local, name)
|
|
3155
|
+
dropped = drop_gradual_narrowing_mark(dropped, name)
|
|
2895
3156
|
return dropped if dropped.equal?(@declaration_sourced)
|
|
2896
3157
|
|
|
2897
3158
|
name = name.to_sym
|
|
@@ -2900,6 +3161,18 @@ module Rigor
|
|
|
2900
3161
|
dropped.dup.delete_if { |ref| ref[0] == :global_copy && ref[1] == name }.freeze
|
|
2901
3162
|
end
|
|
2902
3163
|
|
|
3164
|
+
# `marks` without the local `name`'s `:gradual_narrowing` mark (issue #2656); `marks` itself when it has none.
|
|
3165
|
+
def drop_gradual_narrowing_mark(marks, name)
|
|
3166
|
+
return marks if marks.empty?
|
|
3167
|
+
|
|
3168
|
+
ref = [:gradual_narrowing, name.to_sym]
|
|
3169
|
+
return marks unless marks.include?(ref)
|
|
3170
|
+
|
|
3171
|
+
dropped = marks.dup
|
|
3172
|
+
dropped.delete(ref)
|
|
3173
|
+
dropped.freeze
|
|
3174
|
+
end
|
|
3175
|
+
|
|
2903
3176
|
def add_guard_record(key, pre_guard)
|
|
2904
3177
|
return @guard_records if @guard_records.key?(key)
|
|
2905
3178
|
|