rigortype 0.4.0 → 0.4.2
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 +4 -3
- 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/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- 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 +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- 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/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- 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 +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- 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 +282 -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 +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -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/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- 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 +59 -3
- 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-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 +13 -2
- 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/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -28,6 +28,7 @@ require_relative "fallback"
|
|
|
28
28
|
require_relative "flow_tracer"
|
|
29
29
|
require_relative "index_write_widening"
|
|
30
30
|
require_relative "indexed_narrowing"
|
|
31
|
+
require_relative "key_presence_guard"
|
|
31
32
|
require_relative "jump_targets"
|
|
32
33
|
require_relative "define_method_block_self"
|
|
33
34
|
require_relative "macro_block_self_type"
|
|
@@ -39,6 +40,7 @@ require_relative "optimistic_origin"
|
|
|
39
40
|
require_relative "receiver_alias"
|
|
40
41
|
require_relative "repeated_or_writes"
|
|
41
42
|
require_relative "singleton_object_constant"
|
|
43
|
+
require_relative "refined_dispatch"
|
|
42
44
|
require_relative "stored_block_call"
|
|
43
45
|
require_relative "struct_fold_safety"
|
|
44
46
|
require_relative "unknown_store_widening"
|
|
@@ -648,7 +650,12 @@ module Rigor
|
|
|
648
650
|
# `StatementEvaluator` at class-body and method-body boundaries) or `Dynamic[Top]` at the top level.
|
|
649
651
|
# Class-body `self` is `Singleton[<class>]`; instance-method `self` is `Nominal[<class>]`;
|
|
650
652
|
# singleton-method `self` is `Singleton[<class>]`.
|
|
653
|
+
#
|
|
654
|
+
# Issue #1763 — directly in a `refine X` block body `self` is the refinement module, which Rigor does not
|
|
655
|
+
# model; its `Singleton[X]` stand-in serves implicit-self calls only, so a value read of `self` is untyped.
|
|
651
656
|
def type_of_self_node(_node)
|
|
657
|
+
return dynamic_top if scope.refinement_block_self?
|
|
658
|
+
|
|
652
659
|
scope.self_type || dynamic_top
|
|
653
660
|
end
|
|
654
661
|
|
|
@@ -1811,7 +1818,8 @@ module Rigor
|
|
|
1811
1818
|
# that normal return is `untyped` — the result's declared type — so `loop { e.next }` is not `bot` and
|
|
1812
1819
|
# `loop { x = e.next; break x if x }` keeps its arm beside it.
|
|
1813
1820
|
def call_dispatch_type_for(node, receiver_override: nil)
|
|
1814
|
-
result =
|
|
1821
|
+
result = key_guarded_type(node, call_result_type_for(node, receiver_override: receiver_override))
|
|
1822
|
+
result = loop_completion_type(node, result)
|
|
1815
1823
|
arms = call_break_arm_types(node, receiver_override: receiver_override)
|
|
1816
1824
|
if exactly_once_block_never_completes?(node, receiver_override)
|
|
1817
1825
|
return arms.empty? ? Type::Combinator.bot : Type::Combinator.union(*arms)
|
|
@@ -1821,6 +1829,15 @@ module Rigor
|
|
|
1821
1829
|
widen_optimistic_predicate_constant(node, combined)
|
|
1822
1830
|
end
|
|
1823
1831
|
|
|
1832
|
+
# Issue #1703 — `H[k]` under a true `H.key?(k)` with a non-literal key drops the miss `nil`
|
|
1833
|
+
# ({KeyPresenceGuard.guarded_read}). Only inside the guarded re-walk `call.possible-nil-receiver` runs to
|
|
1834
|
+
# decide whether to withhold a report; the analysis every other rule reads never records a guard.
|
|
1835
|
+
def key_guarded_type(node, type)
|
|
1836
|
+
return type unless node.name == :[]
|
|
1837
|
+
|
|
1838
|
+
KeyPresenceGuard.guarded_read(node, type, scope) || type
|
|
1839
|
+
end
|
|
1840
|
+
|
|
1824
1841
|
# Issue #1172 — the nil-collapsing predicates (`nil?`, `!`, `x == nil`, …) answer a `Constant` that
|
|
1825
1842
|
# encodes the receiver's nil-freeness. When that nil-freeness is only *optimistic* — an
|
|
1826
1843
|
# `%a{implicitly-returns-nil}` read `RbsDispatch` deliberately reads past — the constant is a bet,
|
|
@@ -1896,6 +1913,9 @@ module Rigor
|
|
|
1896
1913
|
# asks nothing further of dispatch. Off (the default) this is one integer read.
|
|
1897
1914
|
Effects::Collector.record_call(node, receiver, scope) if Effects::Collector.active?
|
|
1898
1915
|
|
|
1916
|
+
refined = try_refined_dispatch(node, receiver, arg_types, block_type)
|
|
1917
|
+
return refined if refined
|
|
1918
|
+
|
|
1899
1919
|
literal_send = try_literal_send(node, receiver)
|
|
1900
1920
|
return literal_send if literal_send
|
|
1901
1921
|
|
|
@@ -1927,6 +1947,155 @@ module Rigor
|
|
|
1927
1947
|
dispatch_miss_result(node, receiver, arg_types, block_type)
|
|
1928
1948
|
end
|
|
1929
1949
|
|
|
1950
|
+
# ADR-121 WD2 (issue #1664) — a call through an in-effect Ruby refinement, typed from the winning refine body
|
|
1951
|
+
# with the call's receiver as `self`, ahead of every tier below: each answers from the method the refinement
|
|
1952
|
+
# replaces (a refined `String#upcase` must not fold, a refined `map` must not fold per element, a top-level
|
|
1953
|
+
# `def` must not bind ahead of a refinement of `Object`, a plugin models the class's own method). An unreadable
|
|
1954
|
+
# body, or a list that may hold any refinement, answers `Dynamic[top]`, never the replaced method's signature.
|
|
1955
|
+
# A union receiver is decided per member; a `Dynamic` receiver, a class object and a call no refinement wins
|
|
1956
|
+
# fall through. A call site no refinement is in effect at answers nil at once and records nothing: its answer
|
|
1957
|
+
# cannot change without an edit to its own file.
|
|
1958
|
+
#
|
|
1959
|
+
# ADR-121 WD7 (A2) — inside the span of an activation that puts an opaque module in effect
|
|
1960
|
+
# ({InEffectRefinements.opaque_module?}), any method of any instance may be replaced, so every instance-receiver
|
|
1961
|
+
# call answers `Dynamic[top]`; a class object falls through, as it does below.
|
|
1962
|
+
def try_refined_dispatch(node, receiver, arg_types, block_type)
|
|
1963
|
+
list = scope.in_effect_refinements(node)
|
|
1964
|
+
return nil if list.empty?
|
|
1965
|
+
|
|
1966
|
+
InEffectRefinements.record_refinement_names(node.name)
|
|
1967
|
+
return dynamic_top if refined_instance_receiver?(receiver) && opaque_refinement_span?(node, list)
|
|
1968
|
+
return nil if scope.discovered_refinements.empty?
|
|
1969
|
+
|
|
1970
|
+
refined_indirect_result(node, receiver, list) ||
|
|
1971
|
+
refined_receiver_result(node, receiver, list, arg_types, block_type)
|
|
1972
|
+
end
|
|
1973
|
+
|
|
1974
|
+
# ADR-121 WD4 — `send` / `__send__` / `public_send` with a literal name, `respond_to?` and `method` /
|
|
1975
|
+
# `public_method` with one, and a `&:name` block argument honour refinements too (CRuby `test_refinement.rb`;
|
|
1976
|
+
# probed on Ruby 4.0.5). When the name they reach is refined in effect for the receiver they answer
|
|
1977
|
+
# `Dynamic[top]`: typing them through the arm is deferred, and `respond_to?` must never fold to `false`. A
|
|
1978
|
+
# `&:name` block's receiver is the yielded element, which is not known here, so any in-effect refinement of the
|
|
1979
|
+
# name answers. Only `methods` ignores refinements and keeps its answer.
|
|
1980
|
+
def refined_indirect_result(node, receiver, list)
|
|
1981
|
+
name = REFINED_NAMING_CALLS.include?(node.name) ? literal_method_name_argument(node) : nil
|
|
1982
|
+
return dynamic_top if name && refined_name_for?(receiver, name, list)
|
|
1983
|
+
|
|
1984
|
+
block_name = symbol_block_argument(node)
|
|
1985
|
+
dynamic_top if block_name && refined_anywhere?(block_name, list)
|
|
1986
|
+
end
|
|
1987
|
+
|
|
1988
|
+
REFINED_NAMING_CALLS = Set[:send, :__send__, :public_send, :respond_to?, :method, :public_method].freeze
|
|
1989
|
+
private_constant :REFINED_NAMING_CALLS
|
|
1990
|
+
|
|
1991
|
+
def literal_method_name_argument(node)
|
|
1992
|
+
argument = node.arguments&.arguments&.first
|
|
1993
|
+
name = argument.unescaped if argument.is_a?(Prism::SymbolNode) || argument.is_a?(Prism::StringNode)
|
|
1994
|
+
return nil if name.nil?
|
|
1995
|
+
|
|
1996
|
+
name = name.to_sym
|
|
1997
|
+
InEffectRefinements.record_refinement_names(name)
|
|
1998
|
+
name
|
|
1999
|
+
end
|
|
2000
|
+
|
|
2001
|
+
def symbol_block_argument(node)
|
|
2002
|
+
block = node.block
|
|
2003
|
+
return nil unless block.is_a?(Prism::BlockArgumentNode) && block.expression.is_a?(Prism::SymbolNode)
|
|
2004
|
+
|
|
2005
|
+
name = block.expression.unescaped.to_sym
|
|
2006
|
+
InEffectRefinements.record_refinement_names(name)
|
|
2007
|
+
name
|
|
2008
|
+
end
|
|
2009
|
+
|
|
2010
|
+
def refined_name_for?(receiver, name, list)
|
|
2011
|
+
members = receiver.is_a?(Type::Union) ? receiver.members : [receiver]
|
|
2012
|
+
members.any? do |member|
|
|
2013
|
+
class_name = refined_receiver_class_name(member)
|
|
2014
|
+
!class_name.nil? && !RefinedDispatch.winner(scope, class_name, name, list).nil?
|
|
2015
|
+
end
|
|
2016
|
+
end
|
|
2017
|
+
|
|
2018
|
+
# ADR-121 WD7 — a names-wildcard row counts as a refinement of every name.
|
|
2019
|
+
def refined_anywhere?(name, list)
|
|
2020
|
+
refinements = scope.discovered_refinements
|
|
2021
|
+
wildcard = Scope::DiscoveryIndex::REFINEMENT_WILDCARD
|
|
2022
|
+
return true if list.include?(InEffectRefinements::UNKNOWN) &&
|
|
2023
|
+
refinements.any? { |_refined, methods| methods.key?(name) || methods.key?(wildcard) }
|
|
2024
|
+
|
|
2025
|
+
!RefinedDispatch.targets(refinements, name, list).nil? ||
|
|
2026
|
+
!RefinedDispatch.targets(refinements, wildcard, list).nil?
|
|
2027
|
+
end
|
|
2028
|
+
|
|
2029
|
+
# ADR-121 WD7 — does the call's lexical list (a plugin-declared module is the plugin's declaration and never
|
|
2030
|
+
# opaque) hold an opaque module? `list` is the full list, which is the lexical one when nothing is declared.
|
|
2031
|
+
def opaque_refinement_span?(node, list)
|
|
2032
|
+
lexical = scope.declared_refinements.empty? ? list : scope.in_effect_refinements(node, InEffectRefinements::EMPTY)
|
|
2033
|
+
InEffectRefinements.opaque_in?(scope, lexical)
|
|
2034
|
+
end
|
|
2035
|
+
|
|
2036
|
+
# Does `receiver` (or a member of a union) have a class the refined arm answers for: an instance, not a class
|
|
2037
|
+
# object or `Dynamic`?
|
|
2038
|
+
def refined_instance_receiver?(receiver)
|
|
2039
|
+
members = receiver.is_a?(Type::Union) ? receiver.members : [receiver]
|
|
2040
|
+
members.any? { |member| !refined_receiver_class_name(member).nil? }
|
|
2041
|
+
end
|
|
2042
|
+
|
|
2043
|
+
def refined_receiver_result(node, receiver, list, arg_types, block_type)
|
|
2044
|
+
return refined_member_result(node, receiver, list, arg_types, block_type) unless receiver.is_a?(Type::Union)
|
|
2045
|
+
|
|
2046
|
+
answers = receiver.members.map { |member| refined_member_result(node, member, list, arg_types, block_type) }
|
|
2047
|
+
return nil if answers.none?
|
|
2048
|
+
|
|
2049
|
+
members = receiver.members.each_with_index.map do |member, index|
|
|
2050
|
+
answers[index] || call_result_type_for(node, receiver_override: member)
|
|
2051
|
+
end
|
|
2052
|
+
Type::Combinator.union(*members)
|
|
2053
|
+
end
|
|
2054
|
+
|
|
2055
|
+
def refined_member_result(node, receiver, list, arg_types, block_type)
|
|
2056
|
+
class_name = refined_receiver_class_name(receiver)
|
|
2057
|
+
return nil if class_name.nil?
|
|
2058
|
+
|
|
2059
|
+
winner = RefinedDispatch.winner(scope, class_name, node.name, list)
|
|
2060
|
+
return nil if winner.nil?
|
|
2061
|
+
return dynamic_top if winner == RefinedDispatch::UNKNOWN
|
|
2062
|
+
|
|
2063
|
+
refined_body_return(winner, node.name, Type::Combinator.nominal_of(class_name), arg_types, block_type) ||
|
|
2064
|
+
dynamic_top
|
|
2065
|
+
end
|
|
2066
|
+
|
|
2067
|
+
# The winning refine body's inferred return, re-typed with the receiver's class as `self` and the call's
|
|
2068
|
+
# argument types bound as an undeclared method's are; nil when the body is not readable here.
|
|
2069
|
+
#
|
|
2070
|
+
# A body another file wrote is re-typed with that file's in-effect refinements at the `def` (its own module
|
|
2071
|
+
# among them), so a sibling override it calls answers from its refine body too.
|
|
2072
|
+
def refined_body_return(winner, method_name, self_type, arg_types, block_type)
|
|
2073
|
+
def_node, foreign =
|
|
2074
|
+
InEffectRefinements.refinement_def_with_query(scope, winner.module_name, winner.refined_class, method_name)
|
|
2075
|
+
return nil if def_node.nil?
|
|
2076
|
+
|
|
2077
|
+
refinements = foreign&.at(def_node.location.start_offset) do |name|
|
|
2078
|
+
InEffectRefinements.activated_modules(scope, name)
|
|
2079
|
+
end
|
|
2080
|
+
infer_user_method_return(def_node, self_type, arg_types, yield_type: block_type, refinements: refinements)
|
|
2081
|
+
rescue StandardError
|
|
2082
|
+
nil
|
|
2083
|
+
end
|
|
2084
|
+
|
|
2085
|
+
# The class an instance-side receiver is an instance of, or nil for one a refinement does not answer here: a
|
|
2086
|
+
# class object (refinements of singleton classes are not recorded), `Dynamic`, `top`, or a shape with no class.
|
|
2087
|
+
def refined_receiver_class_name(receiver)
|
|
2088
|
+
case receiver
|
|
2089
|
+
when Type::Nominal, Type::StructInstance, Type::DataInstance then receiver.class_name
|
|
2090
|
+
when Type::Constant then receiver.value.class.name
|
|
2091
|
+
when Type::Tuple then "Array"
|
|
2092
|
+
when Type::HashShape then "Hash"
|
|
2093
|
+
when Type::IntegerRange then "Integer"
|
|
2094
|
+
when Type::FloatRange then "Float"
|
|
2095
|
+
when Type::Refined, Type::Difference then refined_receiver_class_name(receiver.base)
|
|
2096
|
+
end
|
|
2097
|
+
end
|
|
2098
|
+
|
|
1930
2099
|
# The post-dispatch tiers for a call `MethodDispatcher` could not answer, in their historical
|
|
1931
2100
|
# order; extracted from {#call_dispatch_type_for} whole.
|
|
1932
2101
|
def dispatch_miss_result(node, receiver, arg_types, block_type = nil)
|
|
@@ -2280,8 +2449,8 @@ module Rigor
|
|
|
2280
2449
|
def try_user_method_inference(receiver, call_node, arg_types, method_name: call_node.name, block_type: nil)
|
|
2281
2450
|
return nil unless user_inference_receiver?(receiver)
|
|
2282
2451
|
|
|
2283
|
-
def_node, owner =
|
|
2284
|
-
return
|
|
2452
|
+
def_node, owner, raising = resolve_typing_def_with_owner(receiver.class_name, method_name)
|
|
2453
|
+
return every_definer_raises_type(raising, receiver, call_node, arg_types, block_type) if def_node.nil?
|
|
2285
2454
|
|
|
2286
2455
|
result = infer_user_method_return(def_node, receiver, arg_types,
|
|
2287
2456
|
self_fold_safe: fold_safe_call_receiver?(call_node, receiver),
|
|
@@ -2383,8 +2552,7 @@ module Rigor
|
|
|
2383
2552
|
key = method_name.to_sym
|
|
2384
2553
|
return table[key] if table.key?(key)
|
|
2385
2554
|
|
|
2386
|
-
table[key] = scope.singleton_def_through_ancestors(class_name, method_name
|
|
2387
|
-
name_memo: class_graph_buckets[:name])
|
|
2555
|
+
table[key] = scope.singleton_def_through_ancestors(class_name, method_name)
|
|
2388
2556
|
end
|
|
2389
2557
|
|
|
2390
2558
|
# The project-side singleton-method band: a `Foo.bar` call resolved against a `class << …` / `def self.…`
|
|
@@ -2415,13 +2583,12 @@ module Rigor
|
|
|
2415
2583
|
nil
|
|
2416
2584
|
end
|
|
2417
2585
|
|
|
2418
|
-
# ADR-24 slice 2 — resolves `method_name` against `class_name`'s
|
|
2419
|
-
#
|
|
2420
|
-
# ancestors are NOT
|
|
2421
|
-
# `try_user_method_inference` and already covers them;
|
|
2422
|
-
#
|
|
2423
|
-
#
|
|
2424
|
-
# seeds from the project-wide pre-pass. The walk is breadth-first, cycle-guarded, and node-count-capped.
|
|
2586
|
+
# ADR-24 slice 2 — resolves `method_name` against the project definers on `class_name`'s
|
|
2587
|
+
# {Scope::ResolutionChain}, in Ruby's order: prepended modules, the class, its included modules, then the
|
|
2588
|
+
# superclass's chain likewise. RBS-known ancestors are NOT answered here — the `MethodDispatcher` RBS
|
|
2589
|
+
# tier runs before `try_user_method_inference` and already covers them; they sit on the chain as
|
|
2590
|
+
# external entries this lookup passes over. Cross-file: the chain is built from the tables the runner
|
|
2591
|
+
# seeds from the project-wide pre-pass, cycle-guarded and capped at `Scope::ANCESTOR_WALK_LIMIT`.
|
|
2425
2592
|
|
|
2426
2593
|
CLASS_GRAPH_CACHE_KEY = :__rigor_class_graph_cache__
|
|
2427
2594
|
private_constant :CLASS_GRAPH_CACHE_KEY
|
|
@@ -2464,13 +2631,114 @@ module Rigor
|
|
|
2464
2631
|
# is a pure function of the same frozen index — the sibling resolver it walks reads nothing else.
|
|
2465
2632
|
# `singleton_def` is added lazily by {#singleton_def_through_ancestors}'s caller.
|
|
2466
2633
|
slot = [discovery,
|
|
2467
|
-
{
|
|
2468
|
-
yields: {}.compare_by_identity }]
|
|
2634
|
+
{ user_def: {}, typing_def: {}, self_pure: {}.compare_by_identity, yields: {}.compare_by_identity }]
|
|
2469
2635
|
Thread.current[CLASS_GRAPH_CACHE_KEY] = slot
|
|
2470
2636
|
end
|
|
2471
2637
|
slot[1]
|
|
2472
2638
|
end
|
|
2473
2639
|
|
|
2640
|
+
# ADR-119 C2-b1 — the TYPING read of {#resolve_user_def_with_owner}: the same `[def_node, owner]` pair, but
|
|
2641
|
+
# answered through {DefinerResolution} so a chain that does not stand for the name (a conditional definer,
|
|
2642
|
+
# a conditional include, a fork, an external ancestor that may answer first) is `[nil, nil]` and the call
|
|
2643
|
+
# types `Dynamic` instead of from a definer Ruby may not reach. ABSENT is the same `[nil, nil]`: at a typing
|
|
2644
|
+
# site "no definer" and "cannot say" both fall through to `dispatch_miss_result`. Existence reads
|
|
2645
|
+
# ({#resolve_user_def_through_ancestors}, the self-purity scan) stay on the union memo above: an existence
|
|
2646
|
+
# read turning false there could ADD a `call.undefined-method` firing.
|
|
2647
|
+
#
|
|
2648
|
+
# UNKNOWN carries a third element, the `bot` exception's definers ({#every_project_definer}): the def nodes
|
|
2649
|
+
# of every project entry on the chain that defines the name, or nil where the exception cannot apply.
|
|
2650
|
+
def resolve_typing_def_with_owner(class_name, method_name)
|
|
2651
|
+
cache = class_graph_buckets[:typing_def]
|
|
2652
|
+
table = (cache[class_name.to_s] ||= {})
|
|
2653
|
+
key = method_name.to_sym
|
|
2654
|
+
return table[key] if table.key?(key)
|
|
2655
|
+
|
|
2656
|
+
table[key] =
|
|
2657
|
+
case DefinerResolution.resolve(scope, class_name, method_name, :instance, question: :definer)
|
|
2658
|
+
in DefinerResolution::Known(answer: [node, owner])
|
|
2659
|
+
[node, owner]
|
|
2660
|
+
in DefinerResolution::UNKNOWN
|
|
2661
|
+
[nil, nil, every_project_definer(class_name, method_name)]
|
|
2662
|
+
in DefinerResolution::ABSENT
|
|
2663
|
+
[nil, nil]
|
|
2664
|
+
end
|
|
2665
|
+
end
|
|
2666
|
+
|
|
2667
|
+
# ADR-119 C2-b1 errata (the `bot` exception) — an UNKNOWN chain does not say WHICH definer Ruby calls, but
|
|
2668
|
+
# when every project definer of the name on the chain types `bot` (each raises or exits), the call cannot
|
|
2669
|
+
# return whichever of them runs, so it types `bot` and a guard such as `fail_with 'no' unless count` still
|
|
2670
|
+
# narrows. `defs` is {#every_project_definer}'s list; each is inferred with this call's receiver and
|
|
2671
|
+
# arguments, stopping at the first that returns (the answer is then `Dynamic` whatever the rest say).
|
|
2672
|
+
# Accepted cost: an RBS-unknown external ahead of them that really defines the name and returns is a
|
|
2673
|
+
# narrowing false negative; `bot` reports nothing about a value, and that exposure is master's.
|
|
2674
|
+
def every_definer_raises_type(defs, receiver, call_node, arg_types, block_type)
|
|
2675
|
+
return nil if defs.nil?
|
|
2676
|
+
|
|
2677
|
+
self_fold_safe = fold_safe_call_receiver?(call_node, receiver)
|
|
2678
|
+
defs.each do |def_node|
|
|
2679
|
+
result = infer_user_method_return(def_node, receiver, arg_types,
|
|
2680
|
+
self_fold_safe: self_fold_safe, yield_type: block_type)
|
|
2681
|
+
return nil unless result.is_a?(Type::Bot)
|
|
2682
|
+
end
|
|
2683
|
+
Type::Combinator.bot
|
|
2684
|
+
end
|
|
2685
|
+
|
|
2686
|
+
# The def node of every project entry on `class_name`'s instance chain that defines `method_name`, in any
|
|
2687
|
+
# position (the order is what UNKNOWN does not know), or nil where the `bot` exception cannot apply: a cut
|
|
2688
|
+
# chain, no project definer, an entry whose only record of the name has no body (an `attr_*` or a
|
|
2689
|
+
# `define_method` with no `def`), a definer resting on a `possible` or contested fact (another body may be the
|
|
2690
|
+
# one Ruby holds), or an external RBS knows that declares the name. An external RBS does not know is passed
|
|
2691
|
+
# over. Only what the tables hold is read: a `define_method` beside an entry's own `def` of the name, and a
|
|
2692
|
+
# receiver-form `X.include(M)` outside `X`'s body, are not seen (as on the Known path).
|
|
2693
|
+
#
|
|
2694
|
+
# ADR-46: the answer depends on every entry, so each files its class edge and its negative class edge (a new
|
|
2695
|
+
# file reopening it may add a definer or a mixin), and each project entry's {Scope#user_def_for} files the
|
|
2696
|
+
# method edge, positive for a definer and `read_missing(:method, "Owner#name")` for one that lacks the
|
|
2697
|
+
# `def`. `DefinerResolution`'s decline filed none of this past the external that stopped it.
|
|
2698
|
+
def every_project_definer(class_name, method_name)
|
|
2699
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :methods)
|
|
2700
|
+
chain.record(scope)
|
|
2701
|
+
return nil if chain.truncated?
|
|
2702
|
+
|
|
2703
|
+
defs = []
|
|
2704
|
+
declined = false
|
|
2705
|
+
chain.entries.uniq.each do |entry|
|
|
2706
|
+
if entry.external?
|
|
2707
|
+
note_missing_class(entry.raw.to_s.split("::").last)
|
|
2708
|
+
declined = true if external_declares?(entry.candidates, method_name)
|
|
2709
|
+
next
|
|
2710
|
+
end
|
|
2711
|
+
note_missing_class(entry.last_segment)
|
|
2712
|
+
node = scope.user_def_for(entry.name, method_name)
|
|
2713
|
+
defs << node if node
|
|
2714
|
+
declined = true if definer_without_single_body?(entry.name, method_name, node)
|
|
2715
|
+
end
|
|
2716
|
+
declined || defs.empty? ? nil : defs.freeze
|
|
2717
|
+
end
|
|
2718
|
+
|
|
2719
|
+
def note_missing_class(segment)
|
|
2720
|
+
Analysis::DependencyRecorder.read_missing(:class, segment) if Analysis::DependencyRecorder.active?
|
|
2721
|
+
end
|
|
2722
|
+
|
|
2723
|
+
def definer_without_single_body?(owner, method_name, node)
|
|
2724
|
+
discovery = scope.discovery
|
|
2725
|
+
key = [owner, method_name.to_sym]
|
|
2726
|
+
(node.nil? && scope.discovered_method?(owner, method_name, :instance)) ||
|
|
2727
|
+
discovery.possible_method?(owner, method_name, :instance) ||
|
|
2728
|
+
discovery.contested?(:discovered_def_nodes, key)
|
|
2729
|
+
end
|
|
2730
|
+
|
|
2731
|
+
# True where RBS knows one of the names the external's spelling can denote and declares the instance method
|
|
2732
|
+
# in it; a lookup that fails counts as declaring (the exception then does not apply).
|
|
2733
|
+
def external_declares?(candidates, method_name)
|
|
2734
|
+
known = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: scope) }
|
|
2735
|
+
return false if known.nil?
|
|
2736
|
+
|
|
2737
|
+
!Rigor::Reflection.instance_method_definition(known, method_name, scope: scope).nil?
|
|
2738
|
+
rescue StandardError
|
|
2739
|
+
true
|
|
2740
|
+
end
|
|
2741
|
+
|
|
2474
2742
|
def resolve_user_def_through_ancestors(class_name, method_name)
|
|
2475
2743
|
resolve_user_def_with_owner(class_name, method_name).first
|
|
2476
2744
|
end
|
|
@@ -2493,11 +2761,10 @@ module Rigor
|
|
|
2493
2761
|
# nothing but the frozen discovery index, and the `.with` guard in
|
|
2494
2762
|
# {MethodDispatcher::StructMaterialization} needs the SAME answer without threading dispatcher state
|
|
2495
2763
|
# through the dispatcher (#598 review). What stays here is the caching: the run-scoped
|
|
2496
|
-
# `(class_name, method_name)` memo above
|
|
2497
|
-
# whose many methods are resolved
|
|
2764
|
+
# `(class_name, method_name)` memo above. The chain itself is memoised per class by
|
|
2765
|
+
# {Scope::ResolutionChain}, so a class whose many methods are resolved builds it once.
|
|
2498
2766
|
def compute_user_def_with_owner(class_name, method_name)
|
|
2499
|
-
scope.user_def_through_ancestors(class_name, method_name
|
|
2500
|
-
name_memo: class_graph_buckets[:name])
|
|
2767
|
+
scope.user_def_through_ancestors(class_name, method_name)
|
|
2501
2768
|
end
|
|
2502
2769
|
|
|
2503
2770
|
# ADR-57 N5 — overridable-method adoption gate. A self-call resolved to a project `def` whose owner has
|
|
@@ -2671,34 +2938,14 @@ module Rigor
|
|
|
2671
2938
|
index
|
|
2672
2939
|
end
|
|
2673
2940
|
|
|
2674
|
-
# True when `candidate`'s
|
|
2675
|
-
#
|
|
2676
|
-
#
|
|
2677
|
-
#
|
|
2678
|
-
#
|
|
2679
|
-
# identically, sharing the per-edge memo bucket.
|
|
2680
|
-
def enqueue_ancestors(current, queue)
|
|
2681
|
-
scope.enqueue_ancestors(current, queue, class_graph_buckets[:name])
|
|
2682
|
-
end
|
|
2683
|
-
|
|
2941
|
+
# True when `candidate`'s ancestor chain (superclasses + included/prepended modules) reaches `owner` —
|
|
2942
|
+
# i.e. `candidate` is a subclass of an owner class or an includer of an owner module. Asked of the same
|
|
2943
|
+
# {Scope::ResolutionChain} method resolution reads, so name resolution (lexical nesting, RBS-known-ancestor
|
|
2944
|
+
# pruning) is identical; a chain cut at the budget that does not reach `owner` answers false, as the walk
|
|
2945
|
+
# it replaced did. Reachability is the same in both of the chain's worlds.
|
|
2684
2946
|
def related_to_owner?(candidate, owner)
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
seen = {}
|
|
2688
|
-
visited = 0
|
|
2689
|
-
until queue.empty?
|
|
2690
|
-
current = queue.shift
|
|
2691
|
-
next if current.nil? || seen[current]
|
|
2692
|
-
|
|
2693
|
-
return true if current == owner
|
|
2694
|
-
|
|
2695
|
-
seen[current] = true
|
|
2696
|
-
visited += 1
|
|
2697
|
-
return false if visited > Scope::ANCESTOR_WALK_LIMIT
|
|
2698
|
-
|
|
2699
|
-
enqueue_ancestors(current, queue)
|
|
2700
|
-
end
|
|
2701
|
-
false
|
|
2947
|
+
chain = Scope::ResolutionChain.for(scope, candidate.to_s, :instance, :methods)
|
|
2948
|
+
!chain.search(scope) { |entry| entry.name == owner }.nil?
|
|
2702
2949
|
end
|
|
2703
2950
|
|
|
2704
2951
|
INFERENCE_GUARD_KEY = :__rigor_user_method_inference_stack__
|
|
@@ -2881,14 +3128,37 @@ module Rigor
|
|
|
2881
3128
|
# not observable on the body scope, so it does need its own memo-key slot. It is dropped for a def
|
|
2882
3129
|
# that cannot reach a `yield` — nearly all of them — which keeps the key shape constant for the
|
|
2883
3130
|
# methods the memo actually carries and confines the extra dimension to yielding callees.
|
|
2884
|
-
|
|
3131
|
+
# Issue #1703 — inside the guarded re-walk a callee's return summary is still computed with `key?` guards off
|
|
3132
|
+
# ({KeyPresenceGuard.without_guards}): summaries are memoised for the whole run, and one computed with a guard
|
|
3133
|
+
# would reach the analysis every rule reads.
|
|
3134
|
+
#
|
|
3135
|
+
# Issue #1664 — `refinements` are the modules in effect in a refine body another file wrote, which the body scope
|
|
3136
|
+
# carries as declared refinements: its nodes are not this file's, so this file's lexical list cannot answer them.
|
|
3137
|
+
# The return memo and the recursion guard do not key on them, which holds because such a node comes only from
|
|
3138
|
+
# `DefNodeResolver.refinement_query`'s own parse and always carries that file's one list; a caller that types
|
|
3139
|
+
# the same node under another list must add it to the key.
|
|
3140
|
+
def infer_user_method_return(def_node, receiver, arg_types, self_fold_safe: false, yield_type: nil,
|
|
3141
|
+
refinements: nil)
|
|
2885
3142
|
return nil if def_node.body.nil?
|
|
3143
|
+
unless KeyPresenceGuard.active?
|
|
3144
|
+
return infer_user_method_return_unguarded(def_node, receiver, arg_types, self_fold_safe, yield_type,
|
|
3145
|
+
refinements)
|
|
3146
|
+
end
|
|
2886
3147
|
|
|
3148
|
+
KeyPresenceGuard.without_guards do
|
|
3149
|
+
infer_user_method_return_unguarded(def_node, receiver, arg_types, self_fold_safe, yield_type, refinements)
|
|
3150
|
+
end
|
|
3151
|
+
end
|
|
3152
|
+
|
|
3153
|
+
def infer_user_method_return_unguarded(def_node, receiver, arg_types, self_fold_safe, yield_type,
|
|
3154
|
+
refinements = nil)
|
|
2887
3155
|
yield_type = nil unless yield_type && body_yields?(def_node)
|
|
2888
3156
|
body_scope = build_user_method_body_scope(def_node, receiver, arg_types,
|
|
2889
3157
|
self_fold_safe: self_fold_safe)
|
|
2890
3158
|
return nil if body_scope.nil?
|
|
2891
3159
|
|
|
3160
|
+
body_scope = body_scope.with_declared_refinements(refinements) if refinements
|
|
3161
|
+
|
|
2892
3162
|
# Recursion-guard signature. Keyed on `(receiver, method)` only — NOT the argument types. ADR-24 WD5:
|
|
2893
3163
|
# a method whose summary is still being computed resolves to `Dynamic[top]` for that cycle. Keying on
|
|
2894
3164
|
# arg types would let mutual recursion through a `module_function` module (`Acceptance#accepts` →
|
|
@@ -3860,15 +4130,28 @@ module Rigor
|
|
|
3860
4130
|
# Both are precision-only. A `...` outside a forwarding frame (unreachable in valid Ruby) and a double
|
|
3861
4131
|
# splat of anything but a Symbol-keyed closed `HashShape` — a shapeless `Hash[Symbol, V]`, an opaque
|
|
3862
4132
|
# value, a mixed hash — fall back to exactly the pre-#1125 answer.
|
|
4133
|
+
#
|
|
4134
|
+
# Only a list holding a `...` takes the flat_map. Every call is typed through here, and the flat_map's
|
|
4135
|
+
# one-element Array per argument cost `rigor check` about 315K allocations over the v0.3.9 `lib`
|
|
4136
|
+
# ([#1503](https://github.com/rigortype/rigor/issues/1503)).
|
|
3863
4137
|
def call_arg_types(node)
|
|
3864
4138
|
arguments_node = node.arguments
|
|
3865
4139
|
return [] if arguments_node.nil?
|
|
3866
4140
|
|
|
3867
|
-
arguments_node.arguments
|
|
4141
|
+
arguments = arguments_node.arguments
|
|
4142
|
+
return arguments.map { |argument| call_arg_type(argument) } unless forwards_arguments?(arguments)
|
|
4143
|
+
|
|
4144
|
+
arguments.flat_map do |argument|
|
|
3868
4145
|
forwarded_argument_types(argument) || [call_arg_type(argument)]
|
|
3869
4146
|
end
|
|
3870
4147
|
end
|
|
3871
4148
|
|
|
4149
|
+
# Whether a call's argument list holds `...`. Ruby accepts it only last; the scan checks every argument
|
|
4150
|
+
# rather than rely on that.
|
|
4151
|
+
def forwards_arguments?(arguments)
|
|
4152
|
+
arguments.any?(Prism::ForwardingArgumentsNode)
|
|
4153
|
+
end
|
|
4154
|
+
|
|
3872
4155
|
# The frame's argument list when `argument` is `...`, else nil so the caller types it normally.
|
|
3873
4156
|
def forwarded_argument_types(argument)
|
|
3874
4157
|
return nil unless argument.is_a?(Prism::ForwardingArgumentsNode)
|
|
@@ -3930,12 +4213,13 @@ module Rigor
|
|
|
3930
4213
|
method_name: call_node.name,
|
|
3931
4214
|
arg_types: arg_types,
|
|
3932
4215
|
environment: scope.environment,
|
|
3933
|
-
scope: scope
|
|
4216
|
+
scope: scope,
|
|
4217
|
+
call_node: call_node
|
|
3934
4218
|
)
|
|
3935
4219
|
block_return_for(
|
|
3936
4220
|
block_arg, expected,
|
|
3937
4221
|
call_node: call_node,
|
|
3938
|
-
|
|
4222
|
+
narrowing: block_body_self_narrowing(call_node, receiver_type),
|
|
3939
4223
|
repeats: !BLOCK_VALUE_DISCARDING.include?(call_node.name) && block_may_repeat?(call_node, receiver_type)
|
|
3940
4224
|
)
|
|
3941
4225
|
rescue StandardError
|
|
@@ -4002,7 +4286,7 @@ module Rigor
|
|
|
4002
4286
|
block_scope = block_entry_scope(
|
|
4003
4287
|
block_node, param_types,
|
|
4004
4288
|
call_node: call_node,
|
|
4005
|
-
|
|
4289
|
+
narrowing: block_body_self_narrowing(call_node, receiver),
|
|
4006
4290
|
captured: repeating_captured_bindings(block_node, param_types, block_may_repeat?(call_node, receiver))
|
|
4007
4291
|
)
|
|
4008
4292
|
_result, collected = StatementEvaluator.with_break_value_sink do
|
|
@@ -4011,10 +4295,12 @@ module Rigor
|
|
|
4011
4295
|
collected.filter_map { |jump, type| type if targets.key?(jump) }
|
|
4012
4296
|
end
|
|
4013
4297
|
|
|
4014
|
-
# The block body's narrowed `self_type
|
|
4298
|
+
# The block body's narrowing — a {MacroBlockSelfType::Match} carrying the narrowed `self_type` and the declared
|
|
4299
|
+
# refinements — or `nil` to leave the scope contract unchanged.
|
|
4015
4300
|
#
|
|
4016
4301
|
# ADR-16 Tier A: a registered plugin's `block_as_methods` entry matching `(receiver_type,
|
|
4017
|
-
# call_node.name)` narrows to the receiver class's instance type
|
|
4302
|
+
# call_node.name)` narrows to the receiver class's instance type, and puts its `refinements:` in effect in the
|
|
4303
|
+
# body (issue #1667).
|
|
4018
4304
|
#
|
|
4019
4305
|
# Issue #963: `define_method(:name) { ... }` installs its block as an instance method and runs it with
|
|
4020
4306
|
# `self` bound to the receiving instance, so the block body's `self` is the INSTANCE side of a class body's
|
|
@@ -4024,9 +4310,11 @@ module Rigor
|
|
|
4024
4310
|
# matters wherever the block's value is observable, e.g. a project-declared generic `define_method`
|
|
4025
4311
|
# signature that returns the block's own type.
|
|
4026
4312
|
def block_body_self_narrowing(call_node, receiver_type)
|
|
4027
|
-
MacroBlockSelfType.
|
|
4028
|
-
|
|
4029
|
-
|
|
4313
|
+
match = MacroBlockSelfType.match_for(scope: scope, call_node: call_node, receiver_type: receiver_type)
|
|
4314
|
+
return match if match
|
|
4315
|
+
|
|
4316
|
+
defined = DefineMethodBlockSelf.narrow_self_type_for(scope: scope, call_node: call_node)
|
|
4317
|
+
defined && MacroBlockSelfType::Match.new(self_type: defined, refinements: InEffectRefinements::EMPTY)
|
|
4030
4318
|
end
|
|
4031
4319
|
|
|
4032
4320
|
def break_arm_param_types(call_node, receiver)
|
|
@@ -4035,16 +4323,17 @@ module Rigor
|
|
|
4035
4323
|
method_name: call_node.name,
|
|
4036
4324
|
arg_types: call_arg_types(call_node),
|
|
4037
4325
|
environment: scope.environment,
|
|
4038
|
-
scope: scope
|
|
4326
|
+
scope: scope,
|
|
4327
|
+
call_node: call_node
|
|
4039
4328
|
)
|
|
4040
4329
|
end
|
|
4041
4330
|
|
|
4042
|
-
def block_return_for(block_arg, expected, call_node: nil,
|
|
4331
|
+
def block_return_for(block_arg, expected, call_node: nil, narrowing: nil, repeats: false)
|
|
4043
4332
|
case block_arg
|
|
4044
4333
|
when Prism::BlockNode
|
|
4045
4334
|
captured = repeating_captured_bindings(block_arg, expected, repeats)
|
|
4046
4335
|
entry = block_entry_scope(
|
|
4047
|
-
block_arg, expected, call_node: call_node,
|
|
4336
|
+
block_arg, expected, call_node: call_node, narrowing: narrowing, captured: captured
|
|
4048
4337
|
)
|
|
4049
4338
|
type_block_body(block_arg, entry, captured: captured)
|
|
4050
4339
|
when Prism::BlockArgumentNode
|
|
@@ -4060,13 +4349,14 @@ module Rigor
|
|
|
4060
4349
|
# Issue #316 — mirrors `StatementEvaluator#build_block_entry_scope`: the block body's `self` is the
|
|
4061
4350
|
# yielding method's business, so the return-typing pass must see the same unmodelled-self mark. Issue #1358
|
|
4062
4351
|
# — and the same match-global view ({MatchRebinding.block_entry}), which reads the owning `call_node`.
|
|
4063
|
-
def block_entry_scope(block_node, expected, call_node: nil,
|
|
4352
|
+
def block_entry_scope(block_node, expected, call_node: nil, narrowing: nil, captured: nil)
|
|
4064
4353
|
entry = MatchRebinding.block_entry(scope.entering_opaque_block, block_node, call_node)
|
|
4065
4354
|
entry = captured.lay(entry) if captured
|
|
4066
4355
|
block_scope = BlockParameterBinder.new(expected_param_types: expected).bind_onto(block_node, entry)
|
|
4067
|
-
return block_scope unless
|
|
4356
|
+
return block_scope unless narrowing
|
|
4068
4357
|
|
|
4069
|
-
|
|
4358
|
+
keeps_unknown = call_node ? scope.block_self_narrowing_unknown?(call_node) : false
|
|
4359
|
+
MacroBlockSelfType.apply(block_scope, narrowing, keeps_unknown: keeps_unknown)
|
|
4070
4360
|
end
|
|
4071
4361
|
|
|
4072
4362
|
# The #587 (b) captured binding when the call may run the block more than once ({#block_may_repeat?}), and
|