rigortype 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +18 -5
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/02-cli-reference.md +19 -1
- data/docs/manual/03-configuration.md +7 -5
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +41 -4
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +11 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +292 -59
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
- data/lib/rigor/analysis/incremental_session.rb +103 -23
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +241 -37
- data/lib/rigor/analysis/reachability/scan.rb +490 -77
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/run_cache_probe.rb +1 -1
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +62 -13
- data/lib/rigor/analysis/worker_session.rb +8 -5
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +55 -5
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +140 -40
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +6 -3
- data/lib/rigor/cli/unused_command.rb +166 -27
- data/lib/rigor/cli.rb +39 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +61 -2
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +316 -14
- data/lib/rigor/environment/required_features.rb +208 -0
- data/lib/rigor/environment.rb +33 -8
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +43 -3
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +261 -29
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +286 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +96 -14
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +12 -7
- data/lib/rigor/inference/project_patched_scanner.rb +108 -26
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
- data/lib/rigor/inference/refined_dispatch.rb +320 -0
- data/lib/rigor/inference/repeated_or_writes.rb +2 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +827 -65
- data/lib/rigor/inference/statement_evaluator.rb +294 -51
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +87 -5
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +118 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +433 -0
- data/lib/rigor/lens/members.rb +153 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +280 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +40 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +48 -6
- data/lib/rigor/protection/mutation_cache.rb +3 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +211 -21
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +100 -10
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +30 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +9 -4
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +83 -1
data/lib/rigor/scope.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "environment"
|
|
|
5
5
|
require_relative "scope/discovery_index"
|
|
6
6
|
require_relative "scope/resolution_chain"
|
|
7
7
|
require_relative "inference/definer_resolution"
|
|
8
|
+
require_relative "inference/in_effect_refinements"
|
|
8
9
|
require_relative "analysis/fact_store"
|
|
9
10
|
require_relative "analysis/dependency_recorder"
|
|
10
11
|
require_relative "inference/expression_typer"
|
|
@@ -26,12 +27,12 @@ module Rigor
|
|
|
26
27
|
:indexed_narrowings, :method_chain_narrowings,
|
|
27
28
|
:declaration_sourced, :published_constant_sourced,
|
|
28
29
|
:source_path, :discovery, :struct_fold_safe_locals,
|
|
29
|
-
:opaque_block_self, :singleton_class_body, :lexical_nesting,
|
|
30
|
+
:opaque_block_self, :block_self_unknown, :singleton_class_body, :lexical_nesting,
|
|
30
31
|
:dynamic_origins, :local_origins, :ivar_origins,
|
|
31
32
|
:void_origins, :plugin_typed_calls,
|
|
32
33
|
:optimistic_origins, :optimistic_locals, :optimistic_ivars,
|
|
33
34
|
:repeated_or_writes, :match_frame,
|
|
34
|
-
:constant_narrowings, :guard_records, :bot_guard_classes
|
|
35
|
+
:constant_narrowings, :guard_records, :bot_guard_classes, :declared_refinements
|
|
35
36
|
|
|
36
37
|
# ADR-53 Track A — the seed-time discovery tables live on the {DiscoveryIndex} the scope carries by a single
|
|
37
38
|
# reference; the per-table readers stay on Scope so engine call sites and plugins are unaffected by the
|
|
@@ -62,6 +63,20 @@ module Rigor
|
|
|
62
63
|
# Issue #1120 — `{refined class name => {method name => [refining module names]}}`: what each
|
|
63
64
|
# `refine X do … end` block defines, visible only where a `using` of its module is in effect.
|
|
64
65
|
def discovered_refinements = @discovery.discovered_refinements
|
|
66
|
+
|
|
67
|
+
# Issue #1673 (ADR-121 WD1) — the in-effect refinements at `node`: the refining-module names whose refinements
|
|
68
|
+
# Ruby applies there, ordered so a later activation comes later (the later one wins), each once at its first
|
|
69
|
+
# position, each `using`'d module expanded through its includes and prepends in CRuby's activation order.
|
|
70
|
+
# `declared` is a block source's modules, appended and expanded on the same terms; it defaults to this scope's
|
|
71
|
+
# {#declared_refinements}, a plugin-declared refined block's (#1667). An entry may be
|
|
72
|
+
# `Inference::InEffectRefinements::UNKNOWN`, which means any refinement may be in effect. A node another file
|
|
73
|
+
# wrote (a callee body typed under this file's scope) answers `declared` alone. Reads include edges, so it records
|
|
74
|
+
# the dependencies `Inference::InEffectRefinements.activated_modules` names; a consumer that asks for a method
|
|
75
|
+
# name also records `refinement:<name>` (`Inference::InEffectRefinements.refining_modules`).
|
|
76
|
+
def in_effect_refinements(node, declared = @declared_refinements)
|
|
77
|
+
Inference::InEffectRefinements.for_node(self, node, declared)
|
|
78
|
+
end
|
|
79
|
+
|
|
65
80
|
# Issue #1367 — the project's `Inference::GlobalWriteCensus`, a `Set` of frozen entries.
|
|
66
81
|
def discovered_global_write_census = @discovery.discovered_global_write_census
|
|
67
82
|
def discovered_includes = @discovery.discovered_includes
|
|
@@ -182,7 +197,9 @@ module Rigor
|
|
|
182
197
|
# today.
|
|
183
198
|
# - `receiver_name` is the variable's Symbol.
|
|
184
199
|
# - `key` is the Ruby value of the literal index (Symbol / String / Integer). Non-literal keys
|
|
185
|
-
# (`params[field]`)
|
|
200
|
+
# (`params[field]`) have no stable address for a recorded value. A `key?` guard with a non-literal key
|
|
201
|
+
# (issue #1703) records an {Inference::KeyPresenceGuard::KeyExpr} here instead, with `receiver_kind`
|
|
202
|
+
# `:const` allowed; its entry marks presence only.
|
|
186
203
|
IndexedKey = Data.define(:receiver_kind, :receiver_name, :key)
|
|
187
204
|
|
|
188
205
|
# Narrowing key for a no-arg / no-block method-call chain `receiver.method_name` (a "single-hop" chain per A1
|
|
@@ -234,11 +251,11 @@ module Rigor
|
|
|
234
251
|
# class in a project that publishes nothing) allocates none.
|
|
235
252
|
EMPTY_PUBLISHED_CONSTANT_IVARS = Set.new.freeze
|
|
236
253
|
# The index `||=` sites of a repeating block body whose slot an earlier run of the body may have filled,
|
|
237
|
-
# keyed by node identity and laid by a block-return pass
|
|
238
|
-
# ({#with_repeated_or_writes}; `Inference::RepeatedOrWrites` decides which).
|
|
239
|
-
# (`StatementEvaluator#index_compound_write_value`) is withheld at such a site.
|
|
240
|
-
# site, so no rebind or narrowing of a variable drops it, and a join keeps a site
|
|
241
|
-
# mark only ever withholds that reading, whose answer is the narrower one.
|
|
254
|
+
# keyed by node identity and laid at the body's entry by a block-return pass or by `StatementEvaluator`'s
|
|
255
|
+
# statement and write-back passes ({#with_repeated_or_writes}; `Inference::RepeatedOrWrites` decides which).
|
|
256
|
+
# The memoizing `||=` reading (`StatementEvaluator#index_compound_write_value`) is withheld at such a site.
|
|
257
|
+
# The mark belongs to the site, so no rebind or narrowing of a variable drops it, and a join keeps a site
|
|
258
|
+
# either arm holds: the mark only ever withholds that reading, whose answer is the narrower one.
|
|
242
259
|
EMPTY_REPEATED_OR_WRITES = {}.compare_by_identity.freeze
|
|
243
260
|
# Issue #1429 — the narrowing a guard leaves on a constant reference (`STDOUT.is_a?(StringIO)`, `if CONFIG`),
|
|
244
261
|
# keyed by the reference's spelling ({Inference::Narrowing.constant_key}: `"STDOUT"`, `"::Foo::BAR"`). A constant
|
|
@@ -320,6 +337,7 @@ module Rigor
|
|
|
320
337
|
source_path: nil,
|
|
321
338
|
struct_fold_safe_locals: EMPTY_FOLD_SAFE,
|
|
322
339
|
opaque_block_self: false,
|
|
340
|
+
block_self_unknown: false,
|
|
323
341
|
singleton_class_body: false,
|
|
324
342
|
lexical_nesting: nil,
|
|
325
343
|
dynamic_origins: {}.compare_by_identity,
|
|
@@ -334,7 +352,8 @@ module Rigor
|
|
|
334
352
|
match_frame: nil,
|
|
335
353
|
constant_narrowings: EMPTY_CONSTANT_NARROWINGS,
|
|
336
354
|
guard_records: EMPTY_GUARD_RECORDS,
|
|
337
|
-
bot_guard_classes: EMPTY_BOT_GUARD_CLASSES
|
|
355
|
+
bot_guard_classes: EMPTY_BOT_GUARD_CLASSES,
|
|
356
|
+
declared_refinements: Inference::InEffectRefinements::EMPTY
|
|
338
357
|
)
|
|
339
358
|
@environment = environment
|
|
340
359
|
@locals = locals
|
|
@@ -351,6 +370,7 @@ module Rigor
|
|
|
351
370
|
@source_path = source_path
|
|
352
371
|
@struct_fold_safe_locals = struct_fold_safe_locals
|
|
353
372
|
@opaque_block_self = opaque_block_self
|
|
373
|
+
@block_self_unknown = block_self_unknown
|
|
354
374
|
@singleton_class_body = singleton_class_body
|
|
355
375
|
@lexical_nesting = lexical_nesting
|
|
356
376
|
@dynamic_origins = dynamic_origins
|
|
@@ -366,6 +386,7 @@ module Rigor
|
|
|
366
386
|
@constant_narrowings = constant_narrowings
|
|
367
387
|
@guard_records = guard_records
|
|
368
388
|
@bot_guard_classes = bot_guard_classes
|
|
389
|
+
@declared_refinements = declared_refinements
|
|
369
390
|
freeze
|
|
370
391
|
end
|
|
371
392
|
|
|
@@ -378,6 +399,14 @@ module Rigor
|
|
|
378
399
|
self
|
|
379
400
|
end
|
|
380
401
|
|
|
402
|
+
# Issue #1703 — this scope with private copies of the identity-keyed side tables (dynamic, void and optimistic
|
|
403
|
+
# origins, plugin-typed calls), so a second walk from it — the `key?`-guarded re-walk — records into its own
|
|
404
|
+
# tables and never into the ones the file's analysis reads.
|
|
405
|
+
def with_isolated_side_tables
|
|
406
|
+
rebuild(dynamic_origins: @dynamic_origins.dup, void_origins: @void_origins.dup,
|
|
407
|
+
plugin_typed_calls: @plugin_typed_calls.dup, optimistic_origins: @optimistic_origins.dup)
|
|
408
|
+
end
|
|
409
|
+
|
|
381
410
|
def optimistic_local(name) = Inference::OptimisticOrigin.bound_cause(@optimistic_locals[name.to_sym])
|
|
382
411
|
def optimistic_ivar(name) = Inference::OptimisticOrigin.bound_cause(@optimistic_ivars[name.to_sym])
|
|
383
412
|
|
|
@@ -428,6 +457,13 @@ module Rigor
|
|
|
428
457
|
@locals[name.to_sym]
|
|
429
458
|
end
|
|
430
459
|
|
|
460
|
+
# Issue #1840 — replaces the joined bindings of locals a branch merge only narrowed with their pre-branch
|
|
461
|
+
# binding ({Inference::UntypedRejoin}). Unlike {#with_local} this is not a write: the value is the one the
|
|
462
|
+
# branches started from, so every fact, narrowing and mark the join kept about it still holds.
|
|
463
|
+
def with_rejoined_locals(bindings)
|
|
464
|
+
rebuild(locals: @locals.merge(bindings).freeze)
|
|
465
|
+
end
|
|
466
|
+
|
|
431
467
|
def with_local(name, type)
|
|
432
468
|
bind_local(name, type, keep_marks: false)
|
|
433
469
|
end
|
|
@@ -476,6 +512,39 @@ module Rigor
|
|
|
476
512
|
rebuild(self_type: type)
|
|
477
513
|
end
|
|
478
514
|
|
|
515
|
+
# Issue #1717 — installs the `self` a block body runs with when the engine KNOWS it: an ADR-16
|
|
516
|
+
# `block_as_methods:` match, `define_method`, or a `Class.new` / `refine` body (#319). Like
|
|
517
|
+
# {#with_self_type} it leaves the #316 mark alone; it also sets {#block_self_unknown?} to `keeps_unknown`,
|
|
518
|
+
# which a caller passes from {#block_self_narrowing_unknown?}. A nil `type` narrows nothing and keeps it.
|
|
519
|
+
#
|
|
520
|
+
# Issue #1695 — instance variables belong to `self`, so a narrowing onto a DIFFERENT `self` than the enclosing
|
|
521
|
+
# scope's also drops every instance-variable binding the body would otherwise inherit ({#without_ivar_bindings}):
|
|
522
|
+
# a `define_method` block in a class body runs on an instance, and the class body's `@label = nil` is the class
|
|
523
|
+
# object's ivar, not the one the block reads. The body's reads answer `Dynamic` until it writes them. A narrowing
|
|
524
|
+
# that keeps the caller's `self` (an ADR-16 `:lexical` entry) keeps them.
|
|
525
|
+
def with_block_self_type(type, keeps_unknown: false)
|
|
526
|
+
unknown = type.nil? ? @block_self_unknown : keeps_unknown
|
|
527
|
+
return rebuild(self_type: type, block_self_unknown: unknown) if type.nil? || type == @self_type
|
|
528
|
+
|
|
529
|
+
without_ivar_bindings(type, unknown)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Issue #1717 — whether a block-self narrowing for `call_node`, asked from this (the CALLER's) scope, is
|
|
533
|
+
# built on an unknown `self`. `define_method` and an implicit- or `self`-receiver `block_as_methods:` match
|
|
534
|
+
# read the lexical `self`, which inside a block of unknown `self` is the enclosing method's, not the
|
|
535
|
+
# block's; a narrowing keyed on an explicit receiver (`Grape::API.namespace do`) does not depend on it.
|
|
536
|
+
#
|
|
537
|
+
# Issue #1759 — one narrowing is not: a `define_method` written directly in a `refine X` block
|
|
538
|
+
# ({REFINEMENT_BLOCK_SELF}) defines a method of the refinement, which runs on an instance of X, and the
|
|
539
|
+
# `Nominal[X]` it reads off the block's lexical `Singleton[X]` is that `self`.
|
|
540
|
+
def block_self_narrowing_unknown?(call_node)
|
|
541
|
+
return false unless @block_self_unknown
|
|
542
|
+
return false if @block_self_unknown == REFINEMENT_BLOCK_SELF && call_node.name == :define_method
|
|
543
|
+
|
|
544
|
+
receiver = call_node.receiver
|
|
545
|
+
receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
546
|
+
end
|
|
547
|
+
|
|
479
548
|
# Issue #652 — installs the body's REAL `Module.nesting`, innermost first, as recorded at declaration
|
|
480
549
|
# time by `Inference::StatementEvaluator` (`["Admin::UsersController"]` for a compact
|
|
481
550
|
# `class Admin::UsersController`, `["Admin::UsersController", "Admin"]` for the nested spelling). A
|
|
@@ -518,15 +587,52 @@ module Rigor
|
|
|
518
587
|
# `Array#each` without knowing the callee. The flag is set at every block entry that leaves `self_type`
|
|
519
588
|
# unnarrowed and is inherited by every scope derived inside the block; it never leaks past the block,
|
|
520
589
|
# because `eval_call` returns the caller's scope unchanged.
|
|
590
|
+
#
|
|
591
|
+
# Entering also sets {#block_self_unknown?} (issue #1717), the separate mark `call.undefined-method` reads.
|
|
521
592
|
def entering_opaque_block
|
|
522
|
-
return self if @opaque_block_self
|
|
593
|
+
return self if @opaque_block_self && @block_self_unknown == true
|
|
523
594
|
|
|
524
|
-
rebuild(opaque_block_self: true)
|
|
595
|
+
rebuild(opaque_block_self: true, block_self_unknown: true)
|
|
525
596
|
end
|
|
526
597
|
|
|
527
598
|
# True when this scope sits inside a block whose `self` is unmodelled ({#entering_opaque_block}).
|
|
528
599
|
def opaque_block_self? = @opaque_block_self
|
|
529
600
|
|
|
601
|
+
# Issue #1717 — true when this scope sits in a block whose `self` the engine does not know, so an explicit
|
|
602
|
+
# `self.m` there is no evidence `m` is missing: the yielding method may `instance_exec` the block on another
|
|
603
|
+
# object. Read ONLY by `call.undefined-method`'s explicit-`self` exemption, and kept apart from
|
|
604
|
+
# {#opaque_block_self?} so that mark's reach (the top-level-def bind gate) is untouched. Set with
|
|
605
|
+
# {#entering_opaque_block} on the evaluator's block entries and by the indexer's walk of an unentered block
|
|
606
|
+
# ({#with_block_self_unknown}); cleared where a block's `self` is narrowed ({#with_block_self_type}) and, on
|
|
607
|
+
# the indexer's walk, at a `def` / `class` / `module` body, which the evaluator starts from a fresh scope.
|
|
608
|
+
def block_self_unknown? = @block_self_unknown ? true : false
|
|
609
|
+
|
|
610
|
+
# Issue #1759 — the {#block_self_unknown?} value a `refine X do … end` block body is entered with
|
|
611
|
+
# (`with_block_self_type(Singleton[X], keeps_unknown: REFINEMENT_BLOCK_SELF)`). It reads as unknown everywhere
|
|
612
|
+
# `true` does; what it adds is that the body's lexical `self` is X's singleton, which a `define_method` in it
|
|
613
|
+
# reads correctly ({#block_self_narrowing_unknown?}). A block nested in the body takes plain `true`.
|
|
614
|
+
REFINEMENT_BLOCK_SELF = :refinement
|
|
615
|
+
|
|
616
|
+
# Issue #1763 — true directly in a `refine X do … end` block body ({REFINEMENT_BLOCK_SELF}). The body's
|
|
617
|
+
# `self_type` is the `Singleton[X]` its implicit-self calls and `define_method` read, but the block runs on the
|
|
618
|
+
# refinement module, so a `self` read as a value (`x = self`) types as `Dynamic[top]` there.
|
|
619
|
+
def refinement_block_self? = @block_self_unknown == REFINEMENT_BLOCK_SELF
|
|
620
|
+
|
|
621
|
+
# The join of two arms' {#block_self_unknown?} values: `true` if either has it, else either's mark.
|
|
622
|
+
def merged_block_self_unknown(other)
|
|
623
|
+
return true if @block_self_unknown == true || other == true
|
|
624
|
+
|
|
625
|
+
@block_self_unknown || other
|
|
626
|
+
end
|
|
627
|
+
private :merged_block_self_unknown
|
|
628
|
+
|
|
629
|
+
# Issue #1717 — sets {#block_self_unknown?} to `flag`, leaving every other field (the #316 mark included).
|
|
630
|
+
def with_block_self_unknown(flag)
|
|
631
|
+
return self if @block_self_unknown == flag
|
|
632
|
+
|
|
633
|
+
rebuild(block_self_unknown: flag)
|
|
634
|
+
end
|
|
635
|
+
|
|
530
636
|
# Issue #963 — marks the body of a `class << ...` as such. Inside it `self` is the SINGLETON class, which
|
|
531
637
|
# Rigor models with the same `Singleton[X]` carrier a `class X` body gets, so the carrier alone cannot say
|
|
532
638
|
# which of the two a scope came from. The distinction decides what `define_method` does: in a `class << self`
|
|
@@ -544,6 +650,19 @@ module Rigor
|
|
|
544
650
|
rebuild(singleton_class_body: flag)
|
|
545
651
|
end
|
|
546
652
|
|
|
653
|
+
# Issue #1667 (ADR-121 WD5) — this scope with `names` appended to {#declared_refinements}, each once at its first
|
|
654
|
+
# position: the modules a plugin-declared refined block (`block_as_methods:` `refinements:`) runs under. Stamped
|
|
655
|
+
# at block entry and inherited by every scope derived inside the body, nested blocks included, and by a `def` or
|
|
656
|
+
# class body written in it (`StatementEvaluator#build_fresh_body_scope`), as CRuby's cref is.
|
|
657
|
+
def with_declared_refinements(names)
|
|
658
|
+
return self if names.empty?
|
|
659
|
+
|
|
660
|
+
merged = (@declared_refinements | names).freeze
|
|
661
|
+
return self if merged == @declared_refinements
|
|
662
|
+
|
|
663
|
+
rebuild(declared_refinements: merged)
|
|
664
|
+
end
|
|
665
|
+
|
|
547
666
|
# True when this scope IS a `class << ...` body (not merely inside one lexically — a `def` reached from it
|
|
548
667
|
# answers false).
|
|
549
668
|
def singleton_class_body? = @singleton_class_body
|
|
@@ -600,6 +719,45 @@ module Rigor
|
|
|
600
719
|
end
|
|
601
720
|
private :bind_ivar
|
|
602
721
|
|
|
722
|
+
# Issue #1695 — this scope with no instance-variable binding and none of the per-ivar state that rides on one:
|
|
723
|
+
# the narrowings keyed on or rooted at an ivar, its ADR-58 and #667 marks, ADR-82 origins, #286 optimistic marks
|
|
724
|
+
# and #1446 guard records, the state {#bind_ivar} drops for one name. It installs {#with_block_self_type}'s
|
|
725
|
+
# `self_type` and unknown-`self` mark in the same rebuild.
|
|
726
|
+
def without_ivar_bindings(self_type, block_self_unknown)
|
|
727
|
+
rebuild(
|
|
728
|
+
self_type: self_type,
|
|
729
|
+
block_self_unknown: block_self_unknown,
|
|
730
|
+
ivars: EMPTY_VAR_BINDINGS,
|
|
731
|
+
indexed_narrowings: reject_kept(@indexed_narrowings) { |k, _| ivar_indexed_key?(k) },
|
|
732
|
+
method_chain_narrowings: reject_kept(@method_chain_narrowings) { |k, _| k.receiver_kind == :ivar },
|
|
733
|
+
declaration_sourced: reject_kept(@declaration_sourced) { |ref| ref[0] == :ivar },
|
|
734
|
+
published_constant_sourced: reject_kept(@published_constant_sourced) { |ref| ref[0] == :ivar },
|
|
735
|
+
ivar_origins: EMPTY_ORIGINS,
|
|
736
|
+
optimistic_ivars: EMPTY_ORIGINS,
|
|
737
|
+
guard_records: reject_kept(@guard_records) { |key, _| key[0] == :ivar },
|
|
738
|
+
bot_guard_classes: reject_kept(@bot_guard_classes) { |key, _| key[0] == :ivar }
|
|
739
|
+
)
|
|
740
|
+
end
|
|
741
|
+
private :without_ivar_bindings
|
|
742
|
+
|
|
743
|
+
# `collection` without the entries the block selects, or `collection` itself when it selects none. It keeps the
|
|
744
|
+
# collection's class: `Set#reject` answers an Array, which a Set carrier's readers (`merge`, `|`, `==`) reject.
|
|
745
|
+
def reject_kept(collection, &)
|
|
746
|
+
return collection if collection.empty? || collection.none?(&)
|
|
747
|
+
|
|
748
|
+
collection.dup.delete_if(&).freeze
|
|
749
|
+
end
|
|
750
|
+
private :reject_kept
|
|
751
|
+
|
|
752
|
+
# An indexed narrowing of an ivar, or of a `key?` guard's key read from one ({#key_guard_rooted_at?}).
|
|
753
|
+
def ivar_indexed_key?(indexed_key)
|
|
754
|
+
return true if indexed_key.receiver_kind == :ivar
|
|
755
|
+
|
|
756
|
+
key = indexed_key.key
|
|
757
|
+
key.is_a?(Inference::KeyPresenceGuard::KeyExpr) && key.root[0] == :ivar
|
|
758
|
+
end
|
|
759
|
+
private :ivar_indexed_key?
|
|
760
|
+
|
|
603
761
|
# ADR-58 WD1 — used by the method-entry seed to mark an ivar whose only provenance is the class-ivar index.
|
|
604
762
|
# Unlike `with_ivar` this binds the type AND records the declaration-sourced mark in one transition.
|
|
605
763
|
def seed_declaration_sourced_ivar(name, type)
|
|
@@ -904,20 +1062,22 @@ module Rigor
|
|
|
904
1062
|
@repeated_or_writes.key?(node)
|
|
905
1063
|
end
|
|
906
1064
|
|
|
907
|
-
# Regex match-data globals (`$~`, `$&`, `$1..$
|
|
1065
|
+
# Regex match-data globals (`$~`, `$&`, `$1..$N`, the pre/post-match and last-paren back-references). Narrowed
|
|
908
1066
|
# on a successful-`=~` / `case`-`when` match edge (see `Narrowing#regex_match_predicate_scopes`). They live in
|
|
909
1067
|
# the method frame's special-variable slot, which the method's blocks and closures share (issue #1358), so a
|
|
910
1068
|
# later call that may run a match in this frame — a match-capable call, a block that may match, a call once
|
|
911
1069
|
# the frame has made a closure that may — rebinds every one of them, and `eval_call` forgets the narrowed
|
|
912
1070
|
# facts here. Always safe — only drops facts, so a subsequent read falls back to the default `String | nil`.
|
|
913
1071
|
# Program-level `$GLOBAL = ...` seeds use other names and are untouched.
|
|
914
|
-
|
|
915
|
-
|
|
1072
|
+
# Issue #1384 — the numbered groups are matched by name, not listed: a regex may carry more than nine captures, and
|
|
1073
|
+
# `$10` and above are the same slot as `$1`..`$9`, so forgetting must clear them too.
|
|
1074
|
+
MATCH_DATA_GLOBAL_NAME = /\A\$(?:~|&|`|'|\+|[1-9]\d*)\z/
|
|
1075
|
+
private_constant :MATCH_DATA_GLOBAL_NAME
|
|
916
1076
|
|
|
917
1077
|
def forget_match_globals
|
|
918
1078
|
return self unless match_globals_bound?
|
|
919
1079
|
|
|
920
|
-
rebuild(globals: @globals.
|
|
1080
|
+
rebuild(globals: @globals.reject { |name, _| MATCH_DATA_GLOBAL_NAME.match?(name.name) }.freeze)
|
|
921
1081
|
end
|
|
922
1082
|
|
|
923
1083
|
# Issue #1361 — this scope with every bound match-data global rebound to `Dynamic[top]`, and an unbound one left
|
|
@@ -928,14 +1088,16 @@ module Rigor
|
|
|
928
1088
|
return self unless match_globals_bound?
|
|
929
1089
|
|
|
930
1090
|
untyped = Type::Combinator.untyped
|
|
931
|
-
rebound =
|
|
1091
|
+
rebound = @globals.each_with_object({}) do |(name, _), acc|
|
|
1092
|
+
acc[name] = untyped if MATCH_DATA_GLOBAL_NAME.match?(name.name)
|
|
1093
|
+
end
|
|
932
1094
|
rebuild(globals: @globals.merge(rebound).freeze)
|
|
933
1095
|
end
|
|
934
1096
|
|
|
935
1097
|
# True when any match-data global holds a binding, which a match edge or {#untyped_match_globals} makes: the only
|
|
936
1098
|
# state {#forget_match_globals} can drop, and so the gate on every scan that decides whether to.
|
|
937
1099
|
def match_globals_bound?
|
|
938
|
-
!@globals.empty? &&
|
|
1100
|
+
!@globals.empty? && @globals.any? { |name, _| MATCH_DATA_GLOBAL_NAME.match?(name.name) }
|
|
939
1101
|
end
|
|
940
1102
|
|
|
941
1103
|
# Issue #1359 — the last line read, `$_`, lives in the same frame slot as the match globals, and so on the same
|
|
@@ -1954,6 +2116,7 @@ module Rigor
|
|
|
1954
2116
|
source_path: @source_path,
|
|
1955
2117
|
struct_fold_safe_locals: @struct_fold_safe_locals,
|
|
1956
2118
|
opaque_block_self: @opaque_block_self,
|
|
2119
|
+
block_self_unknown: @block_self_unknown,
|
|
1957
2120
|
singleton_class_body: @singleton_class_body,
|
|
1958
2121
|
lexical_nesting: @lexical_nesting,
|
|
1959
2122
|
dynamic_origins: @dynamic_origins,
|
|
@@ -1968,7 +2131,8 @@ module Rigor
|
|
|
1968
2131
|
match_frame: @match_frame,
|
|
1969
2132
|
constant_narrowings: @constant_narrowings,
|
|
1970
2133
|
guard_records: @guard_records,
|
|
1971
|
-
bot_guard_classes: @bot_guard_classes
|
|
2134
|
+
bot_guard_classes: @bot_guard_classes,
|
|
2135
|
+
declared_refinements: @declared_refinements
|
|
1972
2136
|
)
|
|
1973
2137
|
self.class.new(
|
|
1974
2138
|
environment: environment, locals: locals,
|
|
@@ -1982,6 +2146,7 @@ module Rigor
|
|
|
1982
2146
|
source_path: source_path,
|
|
1983
2147
|
struct_fold_safe_locals: struct_fold_safe_locals,
|
|
1984
2148
|
opaque_block_self: opaque_block_self,
|
|
2149
|
+
block_self_unknown: block_self_unknown,
|
|
1985
2150
|
singleton_class_body: singleton_class_body,
|
|
1986
2151
|
lexical_nesting: lexical_nesting,
|
|
1987
2152
|
dynamic_origins: dynamic_origins,
|
|
@@ -1996,7 +2161,8 @@ module Rigor
|
|
|
1996
2161
|
match_frame: match_frame,
|
|
1997
2162
|
constant_narrowings: constant_narrowings,
|
|
1998
2163
|
guard_records: guard_records,
|
|
1999
|
-
bot_guard_classes: bot_guard_classes
|
|
2164
|
+
bot_guard_classes: bot_guard_classes,
|
|
2165
|
+
declared_refinements: declared_refinements
|
|
2000
2166
|
)
|
|
2001
2167
|
end
|
|
2002
2168
|
|
|
@@ -2014,7 +2180,8 @@ module Rigor
|
|
|
2014
2180
|
result.freeze
|
|
2015
2181
|
end
|
|
2016
2182
|
|
|
2017
|
-
|
|
2183
|
+
# One keyword per Scope field, each with its join rule beside it; the size is the field count.
|
|
2184
|
+
def build_joined_scope(joined_locals, joined_ivars, joined_cvars, joined_globals, other) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
2018
2185
|
self.class.new(
|
|
2019
2186
|
environment: @environment,
|
|
2020
2187
|
locals: joined_locals.freeze,
|
|
@@ -2062,6 +2229,9 @@ module Rigor
|
|
|
2062
2229
|
# block entry by `entering_opaque_block` and inherited through `rebuild`), so both arms usually carry
|
|
2063
2230
|
# the same value and the `||` is that value.
|
|
2064
2231
|
opaque_block_self: @opaque_block_self || other.opaque_block_self,
|
|
2232
|
+
# Issue #1717 — the same body property, and `||` keeps the exemption it grants (the FP-safe side).
|
|
2233
|
+
# A plain `true` outranks the refine body's {REFINEMENT_BLOCK_SELF} for the same reason.
|
|
2234
|
+
block_self_unknown: merged_block_self_unknown(other.block_self_unknown),
|
|
2065
2235
|
# Issue #963 — a body property like the two above, so both arms of an in-body merge carry the identical
|
|
2066
2236
|
# value and the `||` is that value. `||` is also the safe direction on its own terms: keeping the mark
|
|
2067
2237
|
# declines the `define_method` narrowing, which is the pre-#963 answer.
|
|
@@ -2086,12 +2256,24 @@ module Rigor
|
|
|
2086
2256
|
# of a merge inside one body carry the same one; `||` keeps it should either arm lack it, since dropping
|
|
2087
2257
|
# it only loses the frame's resets.
|
|
2088
2258
|
match_frame: @match_frame || other.match_frame,
|
|
2259
|
+
# Issue #1667 — a body property stamped at block entry like the frame above, so both arms of a merge carry
|
|
2260
|
+
# the same list; the union keeps either arm's modules, the declining direction.
|
|
2261
|
+
declared_refinements: join_declared_refinements(other),
|
|
2089
2262
|
# Issue #1429 — the guard narrowings of globals and constants and their pre-guard records, and of instance
|
|
2090
2263
|
# variables (#1446).
|
|
2091
2264
|
**join_guard_narrowings(other, joined_globals, joined_ivars)
|
|
2092
2265
|
)
|
|
2093
2266
|
end
|
|
2094
2267
|
|
|
2268
|
+
def join_declared_refinements(other)
|
|
2269
|
+
mine = @declared_refinements
|
|
2270
|
+
theirs = other.declared_refinements
|
|
2271
|
+
return mine if theirs.equal?(mine) || theirs.empty?
|
|
2272
|
+
return theirs if mine.empty?
|
|
2273
|
+
|
|
2274
|
+
(mine | theirs).freeze
|
|
2275
|
+
end
|
|
2276
|
+
|
|
2095
2277
|
def join_guard_narrowings(other, joined_globals, joined_ivars)
|
|
2096
2278
|
joined_constants = join_constant_narrowings(other)
|
|
2097
2279
|
joined = { global: joined_globals, ivar: joined_ivars, constant: joined_constants }
|
|
@@ -2246,11 +2428,19 @@ module Rigor
|
|
|
2246
2428
|
sym_kind = receiver_kind.to_sym
|
|
2247
2429
|
sym_name = receiver_name.to_sym
|
|
2248
2430
|
filtered = @indexed_narrowings.reject do |k, _|
|
|
2249
|
-
k.receiver_kind == sym_kind && k.receiver_name == sym_name
|
|
2431
|
+
(k.receiver_kind == sym_kind && k.receiver_name == sym_name) || key_guard_rooted_at?(k.key, sym_kind, sym_name)
|
|
2250
2432
|
end
|
|
2251
2433
|
filtered.size == @indexed_narrowings.size ? @indexed_narrowings : filtered.freeze
|
|
2252
2434
|
end
|
|
2253
2435
|
|
|
2436
|
+
# Issue #1703 — whether `key` is a `key?` guard's key that is the variable `kind`/`name`, or a chain read from it.
|
|
2437
|
+
def key_guard_rooted_at?(key, kind, name)
|
|
2438
|
+
return false unless key.is_a?(Inference::KeyPresenceGuard::KeyExpr)
|
|
2439
|
+
|
|
2440
|
+
root = key.root
|
|
2441
|
+
root[0] == kind && root[1] == name
|
|
2442
|
+
end
|
|
2443
|
+
|
|
2254
2444
|
# ADR-58 WD1 — set/clear the declaration-sourced provenance mark.
|
|
2255
2445
|
# Issue #667 — the #667 carrier's add/drop pair. Deliberately not reusing
|
|
2256
2446
|
# {#add_declaration_sourced} / {#drop_declaration_sourced_for}: the two sets are joined by opposite
|
|
@@ -57,7 +57,10 @@ module Rigor
|
|
|
57
57
|
inline_generic_class: "sig.skipped.inline-generic-class",
|
|
58
58
|
# ADR-112 WD4 — the member is declared inline and in `sig/`, and the two disagree. A refusal, not a
|
|
59
59
|
# skip: `--write` and `--check` exit 1 until a person reconciles them or passes `--overwrite`.
|
|
60
|
-
inline_differs: "sig.skipped.inline-differs"
|
|
60
|
+
inline_differs: "sig.skipped.inline-differs",
|
|
61
|
+
# #1676 — a plain top-level `def` defines a private `Object` method and has no class to write a
|
|
62
|
+
# declaration under, so it is reported rather than silently dropped. Whether to emit it stays open.
|
|
63
|
+
top_level_def: "sig.skipped.top-level-def"
|
|
61
64
|
}.freeze
|
|
62
65
|
end
|
|
63
66
|
end
|
|
@@ -57,7 +57,8 @@ module Rigor
|
|
|
57
57
|
# a parameter-only inline `# @rbs` annotation) is the ABSENCE of a return statement, not the author's
|
|
58
58
|
# word about it — the opposite of `void`. It is treated like no declaration at all: the inferred return
|
|
59
59
|
# is proposed as `tighter-return`, carrying `untyped` as the declared spelling so `--diff` still shows a
|
|
60
|
-
# declaration existed (#995; see {#declared_untyped?}).
|
|
60
|
+
# declaration existed (#995; see {#declared_untyped?}). A value literal the body proves (Integer, String,
|
|
61
|
+
# Symbol) is widened to its class there unless the `untyped` is only the inline reader's synthesis (#1489).
|
|
61
62
|
# - A `tighter-return` over the class's own declaration replaces the return and nothing else: the rest of
|
|
62
63
|
# the line is the declaration's, spelled as written, so `--overwrite` never changes which calls the
|
|
63
64
|
# signature accepts. A declaration with more than one overload, or one spelled as an `alias`, is left
|
|
@@ -100,6 +101,9 @@ module Rigor
|
|
|
100
101
|
@class_shells = Set.new
|
|
101
102
|
@class_superclasses = {}
|
|
102
103
|
@meta_layouts = {}
|
|
104
|
+
# Issue #1763 — the walked file's tree, and its refine-call query, built only when a `refine` call is walked.
|
|
105
|
+
@walk_root = nil
|
|
106
|
+
@in_effect_refinements = nil
|
|
103
107
|
# Whole-run, NOT per-file: a rendering defect is reported once at the end of the run.
|
|
104
108
|
@unrenderable = []
|
|
105
109
|
# Issue #735 — whole-run too: `{ class name => the superclass token that resolves nowhere }`.
|
|
@@ -475,7 +479,7 @@ module Rigor
|
|
|
475
479
|
# `rigor sig-gen` run. The `check` path recovers each *file* this way (worker_session.rb); sig-gen
|
|
476
480
|
# recovers per-def so the rest of the file's candidates still emit.
|
|
477
481
|
|
|
478
|
-
|
|
482
|
+
classify_collected_def(path, def_node, class_name, kind, scope_index)
|
|
479
483
|
rescue StandardError
|
|
480
484
|
nil
|
|
481
485
|
end
|
|
@@ -486,8 +490,9 @@ module Rigor
|
|
|
486
490
|
|
|
487
491
|
# Walks the AST collecting `(def_node, class_name, kind)` tuples for every `def` Rigor can re-type. Slice 1
|
|
488
492
|
# covered instance `def foo` methods inside a nameable `class` / `module` body. Slice 4 extends this to
|
|
489
|
-
# singleton-side methods via `def self.foo` and `class << self; def foo; end
|
|
490
|
-
# still degrade silently (no
|
|
493
|
+
# singleton-side methods via `def self.foo` and `class << self; def foo; end`. A plain top-level `def` is
|
|
494
|
+
# collected with a nil class and reported as skipped (#1676); DSL-block defs still degrade silently (no
|
|
495
|
+
# nameable receiver).
|
|
491
496
|
#
|
|
492
497
|
# ADR-14 gap-#3 follow-up tracks two extra pieces during the same walk so the Writer can emit kind-correct
|
|
493
498
|
# RBS without guessing:
|
|
@@ -500,7 +505,10 @@ module Rigor
|
|
|
500
505
|
# RBS spelling that matches the dual instance + singleton dispatch the runtime produces.
|
|
501
506
|
def collect_method_definitions(root, scope_index)
|
|
502
507
|
@declared_types = scope_index[root].declared_types
|
|
508
|
+
@walk_root = root
|
|
509
|
+
@in_effect_refinements = nil
|
|
503
510
|
out = []
|
|
511
|
+
@block_depth = 0
|
|
504
512
|
walk_defs(root, [], false, false, out)
|
|
505
513
|
out
|
|
506
514
|
end
|
|
@@ -530,9 +538,22 @@ module Rigor
|
|
|
530
538
|
return
|
|
531
539
|
end
|
|
532
540
|
|
|
541
|
+
walk_def_children(node, prefix, in_singleton_class, module_function_active, out)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# #1676 — a block body is a DSL scope, not the file's top level, so a `def` inside one is not reported
|
|
545
|
+
# as a top-level def. Neither is a `def` in `class << obj` (a non-`self` singleton body reaches here,
|
|
546
|
+
# the `self` one returns above): it defines a singleton method on `obj`, not a private `Object` method.
|
|
547
|
+
def walk_def_children(node, prefix, in_singleton_class, module_function_active, out)
|
|
548
|
+
return if refine_block_call?(node)
|
|
549
|
+
|
|
550
|
+
in_block = node.is_a?(Prism::BlockNode) || node.is_a?(Prism::LambdaNode) ||
|
|
551
|
+
node.is_a?(Prism::SingletonClassNode)
|
|
552
|
+
@block_depth += 1 if in_block
|
|
533
553
|
node.rigor_each_child do |child|
|
|
534
554
|
walk_defs(child, prefix, in_singleton_class, module_function_active, out)
|
|
535
555
|
end
|
|
556
|
+
@block_depth -= 1 if in_block
|
|
536
557
|
end
|
|
537
558
|
|
|
538
559
|
# [#722](https://github.com/rigortype/rigor/issues/722) residue 3 — the qualified name is built by
|
|
@@ -559,6 +580,20 @@ module Rigor
|
|
|
559
580
|
true
|
|
560
581
|
end
|
|
561
582
|
|
|
583
|
+
# Issue #1763 — a `def` (or an `attr_*`) in a `refine X do … end` block defines a method of the refinement
|
|
584
|
+
# module, not of the namespace the block is written in, and RBS has no declaration for a refinement, so both
|
|
585
|
+
# walks skip the whole block: writing `def shout: () -> String` under the enclosing module declared a method
|
|
586
|
+
# that module does not have. Statement and value position alike, since the walks descend every child. The
|
|
587
|
+
# skip is silent, as for a top-level `def`: a `sig.skipped.*` row would name the enclosing class, the very
|
|
588
|
+
# misattribution this avoids. A `refine` call the engine reads as a class's own method (`Class` undefines
|
|
589
|
+
# `refine`, #1689) walks as any other block, as the engine treats it.
|
|
590
|
+
def refine_block_call?(node)
|
|
591
|
+
return false unless node.is_a?(Prism::CallNode) && Inference::ScopeIndexer.refine_call?(node)
|
|
592
|
+
|
|
593
|
+
@in_effect_refinements ||= Inference::InEffectRefinements.new(@walk_root)
|
|
594
|
+
!@in_effect_refinements.class_body_refine?(node)
|
|
595
|
+
end
|
|
596
|
+
|
|
562
597
|
def declined_self_header?(node)
|
|
563
598
|
path = node.constant_path
|
|
564
599
|
path.is_a?(Prism::ConstantPathNode) && path.parent.is_a?(Prism::SelfNode) &&
|
|
@@ -661,7 +696,8 @@ module Rigor
|
|
|
661
696
|
return nil unless node.value.is_a?(Prism::CallNode)
|
|
662
697
|
return nil unless @meta_layouts.key?((prefix + [node.name.to_s]).join("::"))
|
|
663
698
|
|
|
664
|
-
node.value.block
|
|
699
|
+
block = node.value.block
|
|
700
|
+
block.body if block.is_a?(Prism::BlockNode)
|
|
665
701
|
end
|
|
666
702
|
|
|
667
703
|
# Module / class bodies are walked through the `walk_statements` path so `module_function` (no-args)
|
|
@@ -684,7 +720,13 @@ module Rigor
|
|
|
684
720
|
end
|
|
685
721
|
|
|
686
722
|
def collect_def_node(node, prefix, in_singleton_class, module_function_active, out)
|
|
687
|
-
|
|
723
|
+
if prefix.empty?
|
|
724
|
+
# #1676 — a plain top-level `def` defines a private `Object` method. It is surfaced as a skipped
|
|
725
|
+
# candidate (no class to write it under) rather than dropped; `def self.x` and block-body defs stay
|
|
726
|
+
# unreported.
|
|
727
|
+
out << [node, nil, :instance] if node.receiver.nil? && !in_singleton_class && @block_depth.zero?
|
|
728
|
+
return
|
|
729
|
+
end
|
|
688
730
|
|
|
689
731
|
kind = node.receiver.is_a?(Prism::SelfNode) || in_singleton_class ? :singleton : :instance
|
|
690
732
|
class_name = prefix.join("::")
|
|
@@ -915,6 +957,13 @@ module Rigor
|
|
|
915
957
|
end
|
|
916
958
|
end
|
|
917
959
|
|
|
960
|
+
# #1676 — a top-level `def` is collected with no class; it is reported, never typed.
|
|
961
|
+
def classify_collected_def(path, def_node, class_name, kind, scope_index)
|
|
962
|
+
return skipped(path, def_node, class_name, kind, :top_level_def) if class_name.nil?
|
|
963
|
+
|
|
964
|
+
classify_def(path, def_node, class_name, kind, scope_index)
|
|
965
|
+
end
|
|
966
|
+
|
|
918
967
|
def classify_def(path, def_node, class_name, kind, scope_index)
|
|
919
968
|
return nil if visibility_excludes?(def_node, class_name, kind, scope_index)
|
|
920
969
|
return nil if initialize_excludes?(def_node, kind)
|
|
@@ -1368,14 +1417,53 @@ module Rigor
|
|
|
1368
1417
|
declared.is_a?(Type::Dynamic)
|
|
1369
1418
|
end
|
|
1370
1419
|
|
|
1371
|
-
# Proposed without {#
|
|
1372
|
-
#
|
|
1373
|
-
#
|
|
1374
|
-
# `
|
|
1420
|
+
# Proposed without {#tighter?}: a declared `untyped` return carries no information to weigh it against, the
|
|
1421
|
+
# same position a method with no declaration at all is in. Over a declaration the author wrote, though, a
|
|
1422
|
+
# VALUE literal the body proves (`-> 1`, `-> "x"`, `-> :k`) is an implementation detail the `untyped` was
|
|
1423
|
+
# abstracting over, so it is widened to its class (`1 | 2` to `Integer`, `"S" | nil` to `(String | nil)`; #1489).
|
|
1424
|
+
# `nil`, `true` and `false` are honest types and stay, as does a computed `Float`. An `untyped` that is only
|
|
1425
|
+
# the inline reader's synthesis ({#synthesized_declaration?}) is no word of the author's and keeps the
|
|
1426
|
+
# strictest carrier. `declared_return_rbs` carries `"untyped"` so the `[tighter, was: untyped]` print tag
|
|
1427
|
+
# says a declaration existed.
|
|
1375
1428
|
def declared_untyped_candidate(path, def_node, class_name, kind, inferred, method_def, rendered: nil)
|
|
1429
|
+
unless synthesized_declaration?(method_def)
|
|
1430
|
+
widened = widen_value_literals(inferred)
|
|
1431
|
+
unless widened.equal?(inferred)
|
|
1432
|
+
# A `rendered` line was spelled from the unwidened type; there is nothing to rebuild it from.
|
|
1433
|
+
return equivalent(path, def_node, class_name, kind, inferred, "untyped") if rendered
|
|
1434
|
+
|
|
1435
|
+
inferred = widened
|
|
1436
|
+
end
|
|
1437
|
+
end
|
|
1438
|
+
|
|
1376
1439
|
tighter_return_candidate(path, def_node, class_name, kind, inferred, method_def, "untyped", rendered)
|
|
1377
1440
|
end
|
|
1378
1441
|
|
|
1442
|
+
VALUE_LITERAL_CLASSES = [Integer, String, Symbol].freeze
|
|
1443
|
+
private_constant :VALUE_LITERAL_CLASSES
|
|
1444
|
+
|
|
1445
|
+
# The same object back when nothing needed widening.
|
|
1446
|
+
def widen_value_literals(type)
|
|
1447
|
+
case type
|
|
1448
|
+
when Type::Constant
|
|
1449
|
+
return type unless VALUE_LITERAL_CLASSES.any? { |klass| type.value.instance_of?(klass) }
|
|
1450
|
+
|
|
1451
|
+
Type::Combinator.nominal_of(type.value.class.name)
|
|
1452
|
+
when Type::Union
|
|
1453
|
+
members = type.members.map { |member| widen_value_literals(member) }
|
|
1454
|
+
members.each_with_index.all? { |m, i| m.equal?(type.members[i]) } ? type : Type::Combinator.union(*members)
|
|
1455
|
+
else
|
|
1456
|
+
type
|
|
1457
|
+
end
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
# A declaration only the inline reader's synthesis (a `virtual:` buffer) supplies — ADR-93 gives every `def` an
|
|
1461
|
+
# `untyped` member, annotated or not — is no word of the author's about the return, so it keeps the proposal
|
|
1462
|
+
# a method with no declaration at all gets: the strictest carrier the body proves.
|
|
1463
|
+
def synthesized_declaration?(method_def)
|
|
1464
|
+
method_def.defs.all? { |type_def| type_def.member.location&.buffer&.name.to_s.start_with?("virtual:") }
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1379
1467
|
def build_declared_return(method_def)
|
|
1380
1468
|
translated = method_def.method_types.filter_map { |mt| translate_method_type_return(mt) }
|
|
1381
1469
|
return nil if translated.empty?
|
|
@@ -1712,6 +1800,8 @@ module Rigor
|
|
|
1712
1800
|
return
|
|
1713
1801
|
end
|
|
1714
1802
|
when Prism::CallNode
|
|
1803
|
+
return if refine_block_call?(node)
|
|
1804
|
+
|
|
1715
1805
|
collect_attr_call(node, prefix, in_singleton_class, ctx)
|
|
1716
1806
|
end
|
|
1717
1807
|
|
|
@@ -11,8 +11,8 @@ module Rigor
|
|
|
11
11
|
# applicable to that classification.
|
|
12
12
|
#
|
|
13
13
|
# - `path` — the source `.rb` file the def came from.
|
|
14
|
-
# - `class_name` — qualified receiver class name (e.g. `"Foo::Bar"`). `nil` for top-level
|
|
15
|
-
#
|
|
14
|
+
# - `class_name` — qualified receiver class name (e.g. `"Foo::Bar"`). `nil` for a top-level `def`, which is only
|
|
15
|
+
# ever `skipped` (`sig.skipped.top-level-def`, #1676).
|
|
16
16
|
# - `method_name` — the def's `Symbol` name.
|
|
17
17
|
# - `kind` — `:instance` or `:singleton`.
|
|
18
18
|
# - `inferred_return` — `Rigor::Type` instance (or `nil` when the inference pass disqualified the def).
|