rigortype 0.4.4 → 0.4.6
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 +5 -5
- data/docs/handbook/11-sig-gen.md +100 -18
- data/docs/manual/02-cli-reference.md +85 -16
- data/docs/manual/03-configuration.md +12 -9
- data/docs/manual/04-diagnostics.md +17 -6
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +2 -1
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +17 -2
- data/docs/manual/12-caching.md +8 -1
- data/docs/manual/18-removing-dead-code.md +18 -7
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +30 -0
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +102 -1
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +30 -9
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +142 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +115 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +253 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +3 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +64 -4
- data/lib/rigor/analysis/check_rules.rb +319 -21
- data/lib/rigor/analysis/dependency_recorder.rb +11 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/edit_replay.rb +67 -0
- data/lib/rigor/analysis/incremental.rb +86 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +336 -46
- data/lib/rigor/analysis/path_expansion.rb +44 -1
- data/lib/rigor/analysis/reachability/graph.rb +90 -13
- data/lib/rigor/analysis/reachability/scan.rb +279 -39
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +22 -5
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +11 -3
- data/lib/rigor/analysis/runner/pool_coordinator.rb +82 -16
- data/lib/rigor/analysis/runner/project_pre_passes.rb +45 -3
- data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner.rb +222 -111
- data/lib/rigor/analysis/severity_stamp.rb +11 -0
- data/lib/rigor/analysis/worker_session.rb +24 -1
- data/lib/rigor/cache/descriptor.rb +40 -7
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +49 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/check_command.rb +93 -4
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/diff_command.rb +2 -1
- data/lib/rigor/cli/docs_command.rb +3 -2
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +3 -2
- data/lib/rigor/cli/lens_command.rb +5 -5
- data/lib/rigor/cli/mcp_command.rb +2 -1
- data/lib/rigor/cli/options.rb +20 -3
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +7 -1
- data/lib/rigor/cli/plugins_renderer.rb +24 -2
- data/lib/rigor/cli/show_bleedingedge_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +73 -13
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/type_of_project_scope.rb +27 -0
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +72 -16
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +43 -7
- data/lib/rigor/effects/signature_sources.rb +3 -1
- data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
- data/lib/rigor/environment/gated_signature_guard.rb +2 -1
- data/lib/rigor/environment/gated_signature_plan.rb +2 -1
- data/lib/rigor/environment/installed_gem_set.rb +2 -1
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
- data/lib/rigor/environment/rbs_loader.rb +136 -7
- data/lib/rigor/environment/required_features.rb +136 -29
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment.rb +4 -2
- data/lib/rigor/inference/block_call_timing.rb +7 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/callee_mutation_chain.rb +115 -0
- data/lib/rigor/inference/captured_locals.rb +44 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/deep_erasure.rb +138 -0
- data/lib/rigor/inference/def_return_typer.rb +159 -9
- data/lib/rigor/inference/definer_resolution.rb +101 -20
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +97 -18
- data/lib/rigor/inference/global_write_census.rb +36 -1
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/ivar_writer_census.rb +195 -0
- data/lib/rigor/inference/loop_value_record.rb +53 -0
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/match_rebinding/frame.rb +19 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +278 -42
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +53 -20
- data/lib/rigor/inference/method_dispatcher.rb +113 -19
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +149 -42
- data/lib/rigor/inference/mutation_widening.rb +5 -2
- data/lib/rigor/inference/narrowing.rb +116 -11
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
- data/lib/rigor/inference/project_patched_methods.rb +168 -4
- data/lib/rigor/inference/project_patched_mixins.rb +162 -0
- data/lib/rigor/inference/project_patched_scanner.rb +51 -12
- data/lib/rigor/inference/project_patched_visibility.rb +214 -0
- data/lib/rigor/inference/project_seed_tables.rb +121 -0
- data/lib/rigor/inference/project_yielder.rb +128 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
- data/lib/rigor/inference/reflective_ivar_write.rb +6 -6
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +9 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +162 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +1144 -197
- data/lib/rigor/inference/self_call_ivar_writes.rb +29 -12
- data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
- data/lib/rigor/inference/statement_evaluator.rb +1088 -213
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +72 -12
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/language_server/buffer_table.rb +6 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +20 -6
- data/lib/rigor/language_server/project_context.rb +59 -3
- data/lib/rigor/language_server/server.rb +5 -3
- data/lib/rigor/lens/contract_reader.rb +45 -2
- data/lib/rigor/lens/document.rb +2 -1
- data/lib/rigor/lens/file_reader.rb +12 -2
- data/lib/rigor/lens/members.rb +16 -2
- data/lib/rigor/lens/run.rb +34 -7
- data/lib/rigor/mcp/server.rb +11 -3
- data/lib/rigor/plugin/base.rb +29 -13
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/plugin/manifest.rb +29 -3
- data/lib/rigor/plugin/registry.rb +38 -0
- data/lib/rigor/protection/discovery_seed.rb +64 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +36 -0
- data/lib/rigor/scope/discovery_index.rb +5 -0
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
- data/lib/rigor/scope/resolution_chain.rb +40 -5
- data/lib/rigor/scope.rb +59 -7
- data/lib/rigor/sig_gen/alias_index.rb +2 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +465 -49
- data/lib/rigor/sig_gen/layout_index.rb +5 -2
- data/lib/rigor/sig_gen/observation_collector.rb +141 -12
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +16 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- data/lib/rigor/sig_gen/write_reporter.rb +203 -0
- data/lib/rigor/sig_gen/write_result.rb +3 -1
- data/lib/rigor/sig_gen/writer.rb +5 -3
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/combinator.rb +4 -3
- data/lib/rigor/type/constant.rb +54 -3
- data/lib/rigor/type/hash_shape.rb +14 -9
- data/lib/rigor/type/struct_instance.rb +3 -2
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +61 -44
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +20 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +13 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +38 -9
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +4 -2
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +3 -2
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +6 -0
- data/sig/rigor/plugin/additional_initializer.rbs +5 -5
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +10 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +34 -52
- data/sig/rigor/type.rbs +0 -6
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +32 -1
|
@@ -220,7 +220,8 @@ module Rigor
|
|
|
220
220
|
# `call.undefined-method`.
|
|
221
221
|
patched_result =
|
|
222
222
|
unless skip_project_patched
|
|
223
|
-
try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment
|
|
223
|
+
try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment,
|
|
224
|
+
call_node: call_node, scope: scope)
|
|
224
225
|
end
|
|
225
226
|
if patched_result
|
|
226
227
|
scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
|
|
@@ -258,6 +259,22 @@ module Rigor
|
|
|
258
259
|
return discovered_result
|
|
259
260
|
end
|
|
260
261
|
|
|
262
|
+
# Issue #1708 — a `pre_eval:` patch on one of the receiver's ancestors (`class Object; def zz`,
|
|
263
|
+
# `module Enumerable; def sum_by`). Below the RBS tier, whose decline means no nearer RBS declares the
|
|
264
|
+
# method, and below the tiers that answer the receiver's own patch or a gem catalogue entry. A nearer
|
|
265
|
+
# `def` in the project's source (on an RBS-known receiver, which the discovered-method tier leaves to
|
|
266
|
+
# user-method inference) stops the walk: see {#project_source_defines?}. That probe reads the scope's def
|
|
267
|
+
# index, so a single-file scope (`type-of`, `annotate`, an LSP buffer) cannot see a `def` in another file.
|
|
268
|
+
ancestor_patched =
|
|
269
|
+
if !skip_project_patched && scope
|
|
270
|
+
try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment,
|
|
271
|
+
call_node: call_node, scope: scope, through_ancestors: true)
|
|
272
|
+
end
|
|
273
|
+
if ancestor_patched
|
|
274
|
+
scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
|
|
275
|
+
return ancestor_patched
|
|
276
|
+
end
|
|
277
|
+
|
|
261
278
|
# ADR-5 robustness — synthesized-stub-type tier. When the receiver is a type Rigor
|
|
262
279
|
# invented to make an otherwise-unbuildable project signature resolve (a
|
|
263
280
|
# missing-namespace module, or a stub for a referenced-but-undeclared type like an
|
|
@@ -518,10 +535,9 @@ module Rigor
|
|
|
518
535
|
# The question is asked HERE, of the same two tiers `resolve` consults and in the same order, rather
|
|
519
536
|
# than reconstructed in the veto: a name this reports as answered is a name the dispatch really would
|
|
520
537
|
# resolve, and no plugin knowledge moves into the engine's veto. The tiers are the gated
|
|
521
|
-
# `dynamic_return` walk and the ADR-16 synthetic-method index (Tier C `heredoc_templates` emissions
|
|
522
|
-
#
|
|
523
|
-
#
|
|
524
|
-
# this predicate in production yet — it will when #476 lands, with no change here).
|
|
538
|
+
# `dynamic_return` walk and the ADR-16 synthetic-method index (Tier C `heredoc_templates` emissions and
|
|
539
|
+
# the Tier B `Plugin::Macro::TraitRegistry` explosions, which share that table; since #476 the pre-pass
|
|
540
|
+
# explodes a trait module whenever the project's RBS declares it).
|
|
525
541
|
#
|
|
526
542
|
# It is a predicate, not a type: the veto's job is to stop the top-level bind, after which the call
|
|
527
543
|
# falls through to the ordinary dispatch chain and the tier that answered here answers there too. The
|
|
@@ -644,7 +660,13 @@ module Rigor
|
|
|
644
660
|
# `def to_url; "hello"; end` patched onto `String` now resolves `s.to_url` to
|
|
645
661
|
# `Dynamic[Nominal[String]]` instead of the pre-3a `Dynamic[Top]`. Falls back to
|
|
646
662
|
# `Dynamic[Top]` when the heuristic declined (non-literal tail expression).
|
|
647
|
-
|
|
663
|
+
#
|
|
664
|
+
# `through_ancestors:` (issue #1708), with a scope, searches the receiver's ancestors as well, through
|
|
665
|
+
# {ProjectPatchedMethods#lookup_through_ancestors}, stopping at a nearer project-source `def`. A private or
|
|
666
|
+
# protected patch Ruby would not let the call reach ({ProjectPatchedMethods#callable?}) answers nothing, so
|
|
667
|
+
# `1.kp` on `module Kernel; private def kp` reads as the undefined call it is.
|
|
668
|
+
def try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment, # rubocop:disable Metrics/ParameterLists
|
|
669
|
+
call_node: nil, scope: nil, through_ancestors: false)
|
|
648
670
|
registry = environment&.project_patched_methods
|
|
649
671
|
return nil if registry.nil? || registry.empty?
|
|
650
672
|
|
|
@@ -652,14 +674,55 @@ module Rigor
|
|
|
652
674
|
return nil if class_name.nil?
|
|
653
675
|
|
|
654
676
|
kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
|
|
655
|
-
entry = registry
|
|
677
|
+
entry = project_patched_entry(registry, class_name, method_name, kind, environment,
|
|
678
|
+
scope: scope, through_ancestors: through_ancestors)
|
|
656
679
|
return nil if entry.nil?
|
|
680
|
+
|
|
681
|
+
verdict = registry.callable?(entry, explicit: explicit_non_self_receiver?(call_node),
|
|
682
|
+
self_type: scope ? scope.self_type : :unknown,
|
|
683
|
+
rbs_loader: environment.rbs_loader)
|
|
684
|
+
return nil if verdict == false
|
|
685
|
+
return Type::Combinator.untyped if verdict.nil?
|
|
657
686
|
return Type::Combinator.dynamic(entry.return_type) if entry.return_type
|
|
658
687
|
|
|
659
688
|
aliased = project_patched_alias_target(entry, receiver_type, arg_types, block_type, environment)
|
|
660
689
|
aliased ? Type::Combinator.dynamic(aliased) : Type::Combinator.untyped
|
|
661
690
|
end
|
|
662
691
|
|
|
692
|
+
def project_patched_entry(registry, class_name, method_name, kind, environment, scope:, through_ancestors:)
|
|
693
|
+
return registry.lookup(class_name: class_name, method_name: method_name, kind: kind) unless through_ancestors
|
|
694
|
+
|
|
695
|
+
registry.lookup_through_ancestors(
|
|
696
|
+
class_name: class_name, method_name: method_name, kind: kind, rbs_loader: environment.rbs_loader,
|
|
697
|
+
source_defines: ->(owner, side) { project_source_defines?(scope, owner, method_name, side) }
|
|
698
|
+
)
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Issue #1708 — does the project's own source define `method_name` on `owner` (`side` as the ancestor walk
|
|
702
|
+
# reaches it), or on a module the project prepends to or includes into it? Ruby runs that `def` ahead of a
|
|
703
|
+
# patch on a farther ancestor: `class Integer; def nn = "s"` beats `class Object; def nn = nil` in a
|
|
704
|
+
# `pre_eval:` file. The owner's level of its {Scope::ResolutionChain} (its prepends, itself, its includes
|
|
705
|
+
# and their own mixins, or a class object's singleton and its extends) holds those definers; whether one
|
|
706
|
+
# of them defines the name does not depend on their order, so the chain is searched without settling it.
|
|
707
|
+
def project_source_defines?(scope, owner, method_name, side)
|
|
708
|
+
return false unless scope.known_user_class?(owner)
|
|
709
|
+
|
|
710
|
+
chain = Scope::ResolutionChain.for(scope, owner, side, :methods)
|
|
711
|
+
own_level_end = chain.level_starts[1] || chain.entries.size
|
|
712
|
+
found = chain.search(scope, 0, own_level_end) do |entry|
|
|
713
|
+
!entry.external? && source_def?(scope, entry.name, method_name, entry.side)
|
|
714
|
+
end
|
|
715
|
+
!found.nil?
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
def source_def?(scope, owner, method_name, side)
|
|
719
|
+
if side == :singleton
|
|
720
|
+
scope.discovered_method?(owner, method_name, :singleton) || !scope.singleton_def_for(owner, method_name).nil?
|
|
721
|
+
else
|
|
722
|
+
scope.discovered_method?(owner, method_name, :instance) || !scope.user_def_for(owner, method_name).nil?
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
663
726
|
# Issue #1702 — an alias in a `pre_eval:` patch whose old name no earlier patch binding defines
|
|
664
727
|
# (`alias old_plus +` on Integer, or `alias_method :orig_succ, :succ` ahead of the patch's own `def succ`)
|
|
665
728
|
# names the method the class had before the patch, so it answers what that method answers for the same
|
|
@@ -967,6 +1030,7 @@ module Rigor
|
|
|
967
1030
|
|
|
968
1031
|
fallback_receiver = user_class_fallback_receiver(receiver_type, environment)
|
|
969
1032
|
return nil if fallback_receiver.nil?
|
|
1033
|
+
return nil if declared_by_external_ancestor?(receiver_type, environment, context)
|
|
970
1034
|
|
|
971
1035
|
# Preserve the ORIGINAL receiver type as the `self` substitution so `Kernel#dup: () ->
|
|
972
1036
|
# self` and other `self`-returning methods route through Object's RBS while still
|
|
@@ -991,6 +1055,45 @@ module Rigor
|
|
|
991
1055
|
)
|
|
992
1056
|
end
|
|
993
1057
|
|
|
1058
|
+
# Issue #2196 — a project class whose ancestry reaches a core or stdlib class before `Object` (`class FL <
|
|
1059
|
+
# Array`) answers a name that class declares from that class, not from `Object` / `Kernel`: Ruby dispatches
|
|
1060
|
+
# `select { … }` on an `FL` to `Array#select`, never to the private `Kernel#select` (`-> Array[String]`). The
|
|
1061
|
+
# ancestor tiers decline such a name where they cannot type it (`RbsDispatch#returns_the_walked_ancestry?`),
|
|
1062
|
+
# and the `Object` fallback then fabricated `Kernel#select`'s answer, so `fields.detect { |f| f.errors }` read
|
|
1063
|
+
# `f` as `String`. The fallback declines the name instead, which leaves the call `Dynamic[top]`.
|
|
1064
|
+
# {ExternalAncestorResolution.resolve} answers nil for a name only `Object`, `Kernel` or `BasicObject` declares,
|
|
1065
|
+
# so `puts` and `raise` keep resolving.
|
|
1066
|
+
def declared_by_external_ancestor?(receiver_type, environment, context)
|
|
1067
|
+
return false unless receiver_type.is_a?(Type::Nominal)
|
|
1068
|
+
|
|
1069
|
+
scope = context.scope
|
|
1070
|
+
return false if scope.nil?
|
|
1071
|
+
|
|
1072
|
+
memo = external_ancestor_memo(scope, environment)
|
|
1073
|
+
key = [receiver_type.class_name, context.method_name]
|
|
1074
|
+
return memo[key] if memo.key?(key)
|
|
1075
|
+
|
|
1076
|
+
memo[key] = !ExternalAncestorResolution.resolve(receiver_type.class_name, context.method_name, :instance,
|
|
1077
|
+
scope: scope, environment: environment,
|
|
1078
|
+
record_dependencies: false).nil?
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
EXTERNAL_ANCESTOR_MEMO_KEY = :__rigor_user_class_fallback_external_ancestor__
|
|
1082
|
+
private_constant :EXTERNAL_ANCESTOR_MEMO_KEY
|
|
1083
|
+
|
|
1084
|
+
# One slot per discovery index and environment, as `RbsDispatch#ancestor_memo_slot` keeps its own: the answer
|
|
1085
|
+
# reads only the frozen discovery tables and the RBS environment, and its walk is withheld from dependency
|
|
1086
|
+
# recording, so it may be served again for the same pair.
|
|
1087
|
+
def external_ancestor_memo(scope, environment)
|
|
1088
|
+
discovery = scope.discovery
|
|
1089
|
+
slot = Thread.current[EXTERNAL_ANCESTOR_MEMO_KEY]
|
|
1090
|
+
unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
|
|
1091
|
+
slot = [discovery, environment, {}]
|
|
1092
|
+
Thread.current[EXTERNAL_ANCESTOR_MEMO_KEY] = slot
|
|
1093
|
+
end
|
|
1094
|
+
slot[2]
|
|
1095
|
+
end
|
|
1096
|
+
|
|
994
1097
|
# True when the call node carries an explicit receiver that is not the literal `self`.
|
|
995
1098
|
# Such a call cannot legally dispatch to a private method, so the user-class fallback must
|
|
996
1099
|
# skip private signatures rather than return a confidently-wrong type. Returns false for
|
|
@@ -1193,7 +1296,7 @@ module Rigor
|
|
|
1193
1296
|
range_lift = range_new_lift(receiver_type.class_name, arg_types)
|
|
1194
1297
|
return range_lift if range_lift
|
|
1195
1298
|
|
|
1196
|
-
set_lift = set_new_lift(receiver_type.class_name, arg_types)
|
|
1299
|
+
set_lift = set_new_lift(receiver_type.class_name, arg_types, context)
|
|
1197
1300
|
return set_lift if set_lift
|
|
1198
1301
|
|
|
1199
1302
|
hash_lift = hash_new_lift(receiver_type.class_name, arg_types, context&.call_node)
|
|
@@ -1549,19 +1652,10 @@ module Rigor
|
|
|
1549
1652
|
# are given or the single argument is a `Tuple` whose every element is a `Constant[T]`.
|
|
1550
1653
|
# Mirrors `SetFolding#fold_new` but lives here so the `:new` path in
|
|
1551
1654
|
# `try_meta_introspection` can reach it before the RBS tier answers `Nominal[Set]`.
|
|
1552
|
-
def set_new_lift(class_name, arg_types)
|
|
1655
|
+
def set_new_lift(class_name, arg_types, context = nil)
|
|
1553
1656
|
return nil unless class_name == "Set"
|
|
1554
|
-
return Type::Combinator.constant_of(::Set.new) if arg_types.empty?
|
|
1555
|
-
return nil if arg_types.size > 1
|
|
1556
|
-
|
|
1557
|
-
arg = arg_types.first
|
|
1558
|
-
return nil unless arg.is_a?(Type::Tuple)
|
|
1559
|
-
return nil unless arg.elements.all?(Type::Constant)
|
|
1560
1657
|
|
|
1561
|
-
|
|
1562
|
-
Type::Combinator.constant_of(::Set.new(values))
|
|
1563
|
-
rescue StandardError
|
|
1564
|
-
nil
|
|
1658
|
+
SetFolding.fold_new(arg_types, context&.call_node, context&.block_type)
|
|
1565
1659
|
end
|
|
1566
1660
|
|
|
1567
1661
|
# `Regexp.new(pattern)` / `Regexp.new(pattern, opts)` — folds to `Constant[Regexp]` when
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require_relative "../../source/literals"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module ModuleFunctionState
|
|
9
|
+
# Issue #2209 — the `module_function` spellings other than the receiverless call that the deferred-ranges
|
|
10
|
+
# prescan ({ModuleFunctionState.prescan_deferred}) reads: `self.module_function(...)`, and
|
|
11
|
+
# `send(:module_function, ...)` or `__send__(:module_function, ...)` on no receiver or `self`, which Ruby runs
|
|
12
|
+
# as the receiverless call. `public_send` raises on a private method such as `module_function`, so it is not
|
|
13
|
+
# one.
|
|
14
|
+
#
|
|
15
|
+
# A literal name gives the `:singleton` row and a `def` argument the `:both` or `:singleton` row a receiverless
|
|
16
|
+
# call gives. The bare form, and an argument that is neither (`*names`, a local), gives an {ANY_ROW_NAME} row
|
|
17
|
+
# over the call: the prescan reads neither the toggle these spellings set nor such a name, so every singleton
|
|
18
|
+
# copy of the owner is uncertain (`DefinerResolution.module_function_row?`).
|
|
19
|
+
module Spellings
|
|
20
|
+
SEND_CALLS = %i[send __send__].freeze
|
|
21
|
+
private_constant :SEND_CALLS
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# The arguments the spelling `node` passes `module_function`, or nil for any other call.
|
|
26
|
+
def arguments(node)
|
|
27
|
+
arguments = node.arguments&.arguments || []
|
|
28
|
+
return arguments if node.name == :module_function && node.receiver.is_a?(Prism::SelfNode)
|
|
29
|
+
return nil unless SEND_CALLS.include?(node.name)
|
|
30
|
+
return nil unless node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
|
|
31
|
+
return nil unless Source::Literals.symbol_or_string(arguments.first) == :module_function
|
|
32
|
+
|
|
33
|
+
arguments.drop(1)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Appends the rows of the spelling `node` with `arguments` ({.arguments}) to `ranges`, and prescans the rest
|
|
37
|
+
# as {ModuleFunctionState.prescan_deferred} prescans a receiverless call's.
|
|
38
|
+
def prescan(node, arguments, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
39
|
+
owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
|
|
40
|
+
call_span = [node.location.start_offset, node.location.end_offset]
|
|
41
|
+
any = arguments.empty?
|
|
42
|
+
arguments.each do |arg|
|
|
43
|
+
if arg.is_a?(Prism::DefNode)
|
|
44
|
+
kind = ScopeIndexer.def_singleton?(arg, qualified_prefix, in_singleton_class) ? :singleton : :both
|
|
45
|
+
ranges << [arg.location.start_offset, arg.location.end_offset, arg.name, kind, owner]
|
|
46
|
+
elsif (name = Source::Literals.symbol_or_string(arg))
|
|
47
|
+
ranges << [*call_span, name, :singleton, owner]
|
|
48
|
+
else
|
|
49
|
+
any = true
|
|
50
|
+
nested_prescan(arg, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
ranges << [*call_span, ANY_ROW_NAME, :singleton, owner] if any
|
|
54
|
+
nested_prescan(node.block, qualified_prefix, in_singleton_class, offsets, ranges) if node.block
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def nested_prescan(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
58
|
+
ModuleFunctionState.prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges,
|
|
59
|
+
nested: true)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -9,24 +9,36 @@ module Rigor
|
|
|
9
9
|
# The one home for what a receiverless `module_function` call does to the methods around it
|
|
10
10
|
# ([#1507](https://github.com/rigortype/rigor/issues/1507),
|
|
11
11
|
# [#1197](https://github.com/rigortype/rigor/issues/1197)). Four readers used to compute it in four
|
|
12
|
-
# places. They disagree with each other and, in places, with
|
|
13
|
-
# one reader's answer exactly: this module gives the readings one
|
|
14
|
-
# them agree. A change to one reading lands here, beside the
|
|
15
|
-
#
|
|
12
|
+
# places, and the method-visibility table reads it too. They disagree with each other and, in places, with
|
|
13
|
+
# Ruby. Each entry point below reproduces one reader's answer exactly: this module gives the readings one
|
|
14
|
+
# place to live, and does not yet make them agree. A change to one reading lands here, beside the others.
|
|
15
|
+
#
|
|
16
|
+
# In Ruby a bare `module_function` call turns the body's module_function mode on, and a later bare
|
|
17
|
+
# `public`, `private` or `protected` ({.visibility_reset?}) turns it off again (#1968). A named call
|
|
18
|
+
# (`public :x`, `private def y`) leaves the mode alone, and each `module` body starts with it off. A reset
|
|
19
|
+
# written in the body's own statements, or in a plain `begin` or `ensure` there, runs. One nested anywhere
|
|
20
|
+
# else (control flow, a block, a `rescue` clause, an argument) may not, so the mode after it is uncertain.
|
|
21
|
+
# A reset in a nested `class`, `module`, `class <<` or `def` body, a `*_eval` / `*_exec` block or a
|
|
22
|
+
# class-creating block (`Class.new do`, `Struct.new do`, ...) belongs to another scope and does not count.
|
|
16
23
|
#
|
|
17
24
|
# - The singleton def-node table (`ScopeIndexer#walk_singleton_body`) reads
|
|
18
25
|
# {.each_singleton_sibling} and {.each_singleton_copy}. A bare call covers the later direct
|
|
19
|
-
# statements of its own body
|
|
26
|
+
# statements of its own body, up to a reset; after an uncertain one the copy is possible.
|
|
20
27
|
# - The deferred ranges (`ScopeIndexer#walk_deferred_body`) read {.prescan_deferred} and
|
|
21
|
-
# {.deferred_module_function?}. A bare call covers every later def by offset,
|
|
22
|
-
# control flow or a block counts.
|
|
28
|
+
# {.deferred_module_function?}. A bare call covers every later def by offset, up to a later reset,
|
|
29
|
+
# and a call inside control flow or a block counts. An uncertain reset changes nothing here.
|
|
23
30
|
# - The extends table (`ScopeIndexer#record_extend_call`) reads {.extends_self?}. A bare call covers
|
|
24
|
-
# every instance def of the module, in any order.
|
|
31
|
+
# every instance def of the module, in any order, and is never reset.
|
|
25
32
|
# - sig-gen (`SigGen::Generator#walk_statements`) reads {.each_sig_gen_statement} and
|
|
26
33
|
# {.sig_gen_module_function?}. A bare call covers the later statements of its own statement list and
|
|
27
|
-
# what nests in them.
|
|
34
|
+
# what nests in them, up to a reset; after an uncertain one {.sig_gen_uncertain?} says so.
|
|
35
|
+
# - The method-visibility table (`ScopeIndexer#walk_method_visibilities`) reads {.instance_visibility}. It
|
|
36
|
+
# applies a `module_function` call as it applies a `private` one, bare or named: the call makes the instance
|
|
37
|
+
# copy private (#1569), and a later bare `public`, `private` or `protected` sets the running default as it
|
|
38
|
+
# would anyway, so the reset needs no reading of its own. A call or reset that may not run leaves the slots
|
|
39
|
+
# after it contested, as any visibility call there does.
|
|
28
40
|
#
|
|
29
|
-
#
|
|
41
|
+
# Another reader does not come here yet: `rigor-activerecord`'s `ModelDiscoverer#table_name_decorators`
|
|
30
42
|
# (`plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb`) reads a module body's
|
|
31
43
|
# direct statements for its table-name readers. It also accepts `self.module_function`, and there a bare
|
|
32
44
|
# call covers the instance methods written before it as well as after it, while a named call takes only a
|
|
@@ -47,23 +59,60 @@ module Rigor
|
|
|
47
59
|
node.name == :module_function && node.receiver.nil?
|
|
48
60
|
end
|
|
49
61
|
|
|
62
|
+
# The visibility a `module_function` call gives the instance method of each name it covers, bare or named:
|
|
63
|
+
# Ruby makes the instance copy private and adds a public singleton copy, which the instance-side
|
|
64
|
+
# method-visibility table does not hold (#1569).
|
|
65
|
+
def instance_visibility = :private
|
|
66
|
+
|
|
50
67
|
# The toggle form: a `module_function` call with no arguments.
|
|
51
68
|
def bare?(node)
|
|
52
69
|
node.arguments.nil? || node.arguments.arguments.empty?
|
|
53
70
|
end
|
|
54
71
|
|
|
72
|
+
# A bare `public`, `private` or `protected` call, receiverless or on `self`: Ruby ends the module_function
|
|
73
|
+
# mode there (#1968). A call with arguments sets the named methods' visibility and leaves the mode alone.
|
|
74
|
+
def visibility_reset?(node)
|
|
75
|
+
%i[public private protected].include?(node.name) && bare?(node) &&
|
|
76
|
+
(node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The mode after `stmt`, a direct statement of a body, for the readers that walk the statements: `mode`
|
|
80
|
+
# is `true`, `false` or `:uncertain` before it. A {.visibility_reset?} the statement runs ends the mode,
|
|
81
|
+
# and one it may not run (`:maybe_off`, below {.prescan_deferred}) makes an on mode uncertain. A
|
|
82
|
+
# `module_function` nested in the statement is not read here, as before. `nested` says the statement
|
|
83
|
+
# itself may not run, so every reset in it may not either.
|
|
84
|
+
def mode_after(mode, stmt, nested: false)
|
|
85
|
+
return mode unless mode
|
|
86
|
+
|
|
87
|
+
events = []
|
|
88
|
+
prescan_deferred(stmt, EMPTY_PREFIX, false, events, [], nested: nested)
|
|
89
|
+
events.sort_by(&:first).each do |_offset, event|
|
|
90
|
+
case event
|
|
91
|
+
when :off then mode = false
|
|
92
|
+
when :maybe_off then mode = :uncertain if mode == true
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
mode
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The owner prefix {.mode_after} hands the prescan, which records no row a reset needs.
|
|
99
|
+
EMPTY_PREFIX = [].freeze
|
|
100
|
+
|
|
55
101
|
# The singleton def-node table's reading, over one class-ish body's direct statements in source
|
|
56
102
|
# order. `ScopeIndexer#statements_of` builds that list, folding a body-level `rescue`, `else` or
|
|
57
103
|
# `ensure` clause into it. Yields `stmt, module_function_on, named_call` for every statement except a
|
|
58
104
|
# bare call:
|
|
59
105
|
#
|
|
60
|
-
# - `module_function_on`
|
|
61
|
-
#
|
|
106
|
+
# - `module_function_on` is `true` when a bare call precedes the statement with no reset between them,
|
|
107
|
+
# `:uncertain` when a reset that may not run is between them ({.mode_after}), and `false` otherwise.
|
|
62
108
|
# - `named_call` is true for a named call (`module_function :a` or `module_function def x`), which
|
|
63
109
|
# {.each_singleton_copy} resolves; the caller records nothing else for it.
|
|
64
110
|
#
|
|
65
|
-
# A call nested in control flow or a block is not seen.
|
|
66
|
-
|
|
111
|
+
# A `module_function` call nested in control flow or a block is not seen. `conditional` lists the
|
|
112
|
+
# statements of a body-level `rescue` or `else` clause ({.conditional_clause_statements}), which may not
|
|
113
|
+
# run, so a reset there makes the mode uncertain rather than ending it.
|
|
114
|
+
def each_singleton_sibling(statements, conditional = [])
|
|
115
|
+
conditional = Set.new(conditional).compare_by_identity
|
|
67
116
|
module_function_on = false
|
|
68
117
|
statements.each do |stmt|
|
|
69
118
|
if stmt.is_a?(Prism::CallNode) && call?(stmt)
|
|
@@ -75,7 +124,24 @@ module Rigor
|
|
|
75
124
|
next
|
|
76
125
|
end
|
|
77
126
|
yield stmt, module_function_on, false
|
|
127
|
+
module_function_on = mode_after(module_function_on, stmt, nested: conditional.include?(stmt))
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The statements of a body-level `rescue` or `else` clause, which `ScopeIndexer#statements_of` folds into
|
|
132
|
+
# the body's direct statements although they run only on an exception, or only without one. An `ensure`
|
|
133
|
+
# clause runs either way and is not listed.
|
|
134
|
+
def conditional_clause_statements(body)
|
|
135
|
+
return [] unless body.is_a?(Prism::BeginNode)
|
|
136
|
+
|
|
137
|
+
statements = []
|
|
138
|
+
clause = body.rescue_clause
|
|
139
|
+
while clause
|
|
140
|
+
statements.concat(ScopeIndexer.statements_of(clause.statements))
|
|
141
|
+
clause = clause.subsequent
|
|
78
142
|
end
|
|
143
|
+
statements.concat(ScopeIndexer.statements_of(body.else_clause.statements)) if body.else_clause
|
|
144
|
+
statements
|
|
79
145
|
end
|
|
80
146
|
|
|
81
147
|
# The defs a named call copies onto the module's singleton, for the singleton def-node table. Ruby
|
|
@@ -121,10 +187,11 @@ module Rigor
|
|
|
121
187
|
alias_method define_method attr attr_reader attr_writer attr_accessor remove_method undef_method
|
|
122
188
|
].freeze
|
|
123
189
|
|
|
124
|
-
# The deferred-ranges reading, over one class-ish body's subtree. Appends to `offsets`
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
190
|
+
# The deferred-ranges reading, over one class-ish body's subtree. Appends to `offsets` an
|
|
191
|
+
# `[offset, :on]` event for the start of every bare call, and for every {.visibility_reset?} an
|
|
192
|
+
# `[offset, :off]` event where it runs or a `[offset, :maybe_off]` one where it may not: `nested` is true
|
|
193
|
+
# once the walk has left the body's statements, a plain `begin` and an `ensure`. Appends to `ranges` one
|
|
194
|
+
# row per named call, in the table's `[start, end, name, kind, owner]` shape:
|
|
128
195
|
#
|
|
129
196
|
# - a `:name` argument gives a `:singleton` row over the call itself, because the copy happens at
|
|
130
197
|
# the call and not at the def;
|
|
@@ -134,33 +201,36 @@ module Rigor
|
|
|
134
201
|
# The prescan looks through control flow and blocks, because a call that ran there still flips the
|
|
135
202
|
# later defs. It stops at a nested class, module, `class <<` or def body, where the call targets
|
|
136
203
|
# another module. It skips an `END` body, which runs after every def, and a `*_eval` / `*_exec`
|
|
137
|
-
# block, whose self is the receiver's module
|
|
138
|
-
#
|
|
204
|
+
# block, whose self is the receiver's module, and a class-creating block (`Class.new do`, `Struct.new do`,
|
|
205
|
+
# `Data.define do`, `Module.new do`), whose self is the new class; such a block gets its own prescan as a
|
|
206
|
+
# body where the walk opens one.
|
|
139
207
|
#
|
|
140
208
|
# `qualified_prefix` names the owner, and `in_singleton_class` says whether the body is a
|
|
141
209
|
# `class <<` body; both feed `ScopeIndexer.def_singleton?`.
|
|
142
|
-
def prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
210
|
+
def prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges, nested: false)
|
|
143
211
|
return unless node.is_a?(Prism::Node)
|
|
144
212
|
return if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) ||
|
|
145
213
|
node.is_a?(Prism::SingletonClassNode) || node.is_a?(Prism::DefNode) ||
|
|
146
214
|
node.is_a?(Prism::PostExecutionNode)
|
|
215
|
+
return if node.is_a?(Prism::CallNode) &&
|
|
216
|
+
prescanned_call?(node, qualified_prefix, in_singleton_class, offsets, ranges, nested)
|
|
147
217
|
|
|
148
|
-
|
|
149
|
-
return prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges) if call?(node)
|
|
150
|
-
if ScopeIndexer.receiver_eval_call?(node)
|
|
151
|
-
return prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
218
|
+
nested ||= !RUN_IN_PLACE.include?(node.class)
|
|
155
219
|
node.rigor_each_child do |child|
|
|
156
|
-
prescan_deferred(child, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
220
|
+
prescan_deferred(child, qualified_prefix, in_singleton_class, offsets, ranges, nested: nested)
|
|
157
221
|
end
|
|
158
222
|
end
|
|
159
223
|
|
|
160
|
-
#
|
|
161
|
-
|
|
224
|
+
# The nodes whose statements run where they are written, so a reset there runs ({.prescan_deferred}).
|
|
225
|
+
RUN_IN_PLACE = [Prism::StatementsNode, Prism::BeginNode, Prism::EnsureNode].freeze
|
|
226
|
+
|
|
227
|
+
# Whether the deferred-ranges reading makes a def starting at `start` a module function: the last
|
|
228
|
+
# `:on` or `:off` event {.prescan_deferred} recorded before it, at any depth the prescan entered, is
|
|
229
|
+
# `:on`. A `:maybe_off` leaves the answer as it was: the row stays `:both`, the reading that counts the
|
|
230
|
+
# def as run on the singleton side too, since the table has no possible row for it.
|
|
162
231
|
def deferred_module_function?(offsets, start)
|
|
163
|
-
offsets.
|
|
232
|
+
last = offsets.select { |offset, event| offset < start && event != :maybe_off }.max_by(&:first)
|
|
233
|
+
last ? last[1] == :on : false
|
|
164
234
|
end
|
|
165
235
|
|
|
166
236
|
# The extends table's reading. A bare call makes the module extend itself, so every instance def the
|
|
@@ -174,10 +244,12 @@ module Rigor
|
|
|
174
244
|
end
|
|
175
245
|
|
|
176
246
|
# sig-gen's reading, over one statement list. Yields every statement except a bare call, with
|
|
177
|
-
# whether a bare call came before it in this list or `active` was already
|
|
247
|
+
# whether a bare call came before it in this list or `active` was already on at entry: `true`, `false`
|
|
248
|
+
# or `:uncertain` after a reset that may not run ({.mode_after}). The
|
|
178
249
|
# generator passes the enclosing list's answer into an `if`, a block or a `begin`, but not into a
|
|
179
250
|
# nested class, module or `class << self`. A bare call in a nested list covers only that list's
|
|
180
|
-
# later statements.
|
|
251
|
+
# later statements. A reset ends it for the statements after it, and a named call is not a directive at
|
|
252
|
+
# all.
|
|
181
253
|
def each_sig_gen_statement(statements, active)
|
|
182
254
|
statements.each do |stmt|
|
|
183
255
|
if sig_gen_directive?(stmt)
|
|
@@ -185,6 +257,7 @@ module Rigor
|
|
|
185
257
|
next
|
|
186
258
|
end
|
|
187
259
|
yield stmt, active
|
|
260
|
+
active = mode_after(active, stmt)
|
|
188
261
|
end
|
|
189
262
|
end
|
|
190
263
|
|
|
@@ -196,7 +269,13 @@ module Rigor
|
|
|
196
269
|
# Whether sig-gen renders a def as `def self?.name`: an instance-side def under an active directive,
|
|
197
270
|
# in a class or a module alike. `kind` is `:singleton` for `def self.x` and a `class << self` body.
|
|
198
271
|
def sig_gen_module_function?(active, kind)
|
|
199
|
-
active && kind == :instance
|
|
272
|
+
active == true && kind == :instance
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Whether sig-gen cannot tell a def's kind: an instance-side def after a reset that may not run, which
|
|
276
|
+
# is a module function only if the reset did not run.
|
|
277
|
+
def sig_gen_uncertain?(active, kind)
|
|
278
|
+
active == :uncertain && kind == :instance
|
|
200
279
|
end
|
|
201
280
|
|
|
202
281
|
# Yields each instance-method name `node` may rebind on the module whose body holds it, or
|
|
@@ -239,10 +318,34 @@ module Rigor
|
|
|
239
318
|
end
|
|
240
319
|
end
|
|
241
320
|
|
|
321
|
+
# Issue #2209 — the row name of a deferred-ranges row that stands for every name of its owner
|
|
322
|
+
# ({Spellings}).
|
|
323
|
+
ANY_ROW_NAME = :*
|
|
324
|
+
|
|
325
|
+
# Issue #2209 — the deferred-ranges row names that make `method_name` a module function on their owner: its
|
|
326
|
+
# own and {ANY_ROW_NAME}.
|
|
327
|
+
def row_names(method_name) = [method_name.to_sym, ANY_ROW_NAME]
|
|
328
|
+
|
|
329
|
+
# The prescan's arms for a call; true when the arm took the call and its subtree.
|
|
330
|
+
def prescanned_call?(node, qualified_prefix, in_singleton_class, offsets, ranges, nested)
|
|
331
|
+
if call?(node)
|
|
332
|
+
prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
333
|
+
elsif (arguments = Spellings.arguments(node))
|
|
334
|
+
Spellings.prescan(node, arguments, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
335
|
+
elsif visibility_reset?(node)
|
|
336
|
+
offsets << [node.location.start_offset, nested ? :maybe_off : :off]
|
|
337
|
+
elsif ScopeIndexer.receiver_eval_call?(node) || (node.block && ScopeIndexer.meta_new_constant_rvalue?(node))
|
|
338
|
+
prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
339
|
+
else
|
|
340
|
+
return false
|
|
341
|
+
end
|
|
342
|
+
true
|
|
343
|
+
end
|
|
344
|
+
|
|
242
345
|
# The prescan's call arm.
|
|
243
346
|
def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
244
347
|
if bare?(node)
|
|
245
|
-
offsets << node.location.start_offset
|
|
348
|
+
offsets << [node.location.start_offset, :on]
|
|
246
349
|
else
|
|
247
350
|
owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
|
|
248
351
|
node.arguments&.arguments&.each do |arg|
|
|
@@ -252,20 +355,22 @@ module Rigor
|
|
|
252
355
|
elsif (name = symbol_argument_name(arg))
|
|
253
356
|
ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
|
|
254
357
|
else
|
|
255
|
-
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
358
|
+
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges, nested: true)
|
|
256
359
|
end
|
|
257
360
|
end
|
|
258
361
|
end
|
|
259
362
|
return unless node.block
|
|
260
363
|
|
|
261
|
-
prescan_deferred(node.block, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
364
|
+
prescan_deferred(node.block, qualified_prefix, in_singleton_class, offsets, ranges, nested: true)
|
|
262
365
|
end
|
|
263
366
|
|
|
264
367
|
# The prescan's eval arm: only the call's receiver and arguments keep this module's context.
|
|
265
368
|
def prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
266
|
-
|
|
369
|
+
if node.receiver
|
|
370
|
+
prescan_deferred(node.receiver, qualified_prefix, in_singleton_class, offsets, ranges, nested: true)
|
|
371
|
+
end
|
|
267
372
|
node.arguments&.arguments&.each do |arg|
|
|
268
|
-
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
373
|
+
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges, nested: true)
|
|
269
374
|
end
|
|
270
375
|
end
|
|
271
376
|
|
|
@@ -274,8 +379,10 @@ module Rigor
|
|
|
274
379
|
Source::Literals.symbol_or_string(arg)
|
|
275
380
|
end
|
|
276
381
|
|
|
277
|
-
private_class_method :prescan_call, :prescan_eval_call, :symbol_argument_name,
|
|
278
|
-
:each_own_rebound_name, :each_rebinding_call_name
|
|
382
|
+
private_class_method :prescan_call, :prescanned_call?, :prescan_eval_call, :symbol_argument_name,
|
|
383
|
+
:each_rebound_name, :each_own_rebound_name, :each_rebinding_call_name
|
|
279
384
|
end
|
|
280
385
|
end
|
|
281
386
|
end
|
|
387
|
+
|
|
388
|
+
require_relative "module_function_state/spellings"
|
|
@@ -7,6 +7,7 @@ require_relative "../source/node_children"
|
|
|
7
7
|
require_relative "content_join"
|
|
8
8
|
require_relative "hash_lookup_mutation"
|
|
9
9
|
require_relative "mutation_rejoin"
|
|
10
|
+
require_relative "deep_erasure"
|
|
10
11
|
require_relative "receiver_alias"
|
|
11
12
|
require_relative "refinement_mutation"
|
|
12
13
|
require_relative "rewrite_mutation"
|
|
@@ -200,12 +201,15 @@ module Rigor
|
|
|
200
201
|
# `Prism::BlockNode`s — each block is processed by its own `eval_call`.
|
|
201
202
|
def widen_after_block(call_node:, outer_scope:)
|
|
202
203
|
block = call_node.block
|
|
204
|
+
return DeepErasure.widen_symbol_proc(call_node, outer_scope) if DeepErasure.symbol_proc_mutation?(block)
|
|
203
205
|
return outer_scope unless block.is_a?(Prism::BlockNode)
|
|
204
206
|
|
|
205
207
|
body = block.body
|
|
206
208
|
return outer_scope if body.nil?
|
|
207
209
|
|
|
208
|
-
walk_for_outer_mutations(body, outer_scope)
|
|
210
|
+
scope = walk_for_outer_mutations(body, outer_scope)
|
|
211
|
+
names = DeepErasure.parameter_names(block.parameters)
|
|
212
|
+
DeepErasure.widen_element_aliases(call_node.receiver, block.body, names, scope)
|
|
209
213
|
end
|
|
210
214
|
|
|
211
215
|
def walk_for_outer_mutations(node, scope)
|
|
@@ -257,7 +261,6 @@ module Rigor
|
|
|
257
261
|
Prism::ClassVariableReadNode => %i[cvar with_cvar],
|
|
258
262
|
Prism::GlobalVariableReadNode => %i[global with_global]
|
|
259
263
|
})
|
|
260
|
-
private_constant :ALIAS_ACCESSORS
|
|
261
264
|
|
|
262
265
|
def widen_alias_read(method_name, read, scope, values: :widen, arg_types: NO_ARG_TYPES)
|
|
263
266
|
getter, builder = ALIAS_ACCESSORS[read.class]
|