rigortype 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +18 -5
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/02-cli-reference.md +19 -1
- data/docs/manual/03-configuration.md +7 -5
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +41 -4
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +11 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +292 -59
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
- data/lib/rigor/analysis/incremental_session.rb +103 -23
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +241 -37
- data/lib/rigor/analysis/reachability/scan.rb +490 -77
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/run_cache_probe.rb +1 -1
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +62 -13
- data/lib/rigor/analysis/worker_session.rb +8 -5
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +55 -5
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +140 -40
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +6 -3
- data/lib/rigor/cli/unused_command.rb +166 -27
- data/lib/rigor/cli.rb +39 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +61 -2
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +316 -14
- data/lib/rigor/environment/required_features.rb +208 -0
- data/lib/rigor/environment.rb +33 -8
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +43 -3
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +261 -29
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +286 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +96 -14
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +12 -7
- data/lib/rigor/inference/project_patched_scanner.rb +108 -26
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
- data/lib/rigor/inference/refined_dispatch.rb +320 -0
- data/lib/rigor/inference/repeated_or_writes.rb +2 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +827 -65
- data/lib/rigor/inference/statement_evaluator.rb +294 -51
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +87 -5
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +118 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +433 -0
- data/lib/rigor/lens/members.rb +153 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +280 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +40 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +48 -6
- data/lib/rigor/protection/mutation_cache.rb +3 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +211 -21
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +100 -10
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +30 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +9 -4
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +83 -1
|
@@ -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,173 @@ 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 and a call no refinement wins fall through. A
|
|
1956
|
+
# class object walks its own lookup (issue #1752): under `refine(Object) { def oo = :o }` a refinement that wins
|
|
1957
|
+
# `Integer.oo` answers `Dynamic[top]` ({#refined_class_object_result}). A call site no refinement is in effect
|
|
1958
|
+
# at answers nil at once and records nothing: its answer cannot change without an edit to its own file.
|
|
1959
|
+
#
|
|
1960
|
+
# ADR-121 WD7 (A2) — inside the span of an activation that puts an opaque module in effect
|
|
1961
|
+
# ({InEffectRefinements.opaque_module?}), any method of any object may be replaced, so every call on an instance
|
|
1962
|
+
# or a class object answers `Dynamic[top]`.
|
|
1963
|
+
def try_refined_dispatch(node, receiver, arg_types, block_type)
|
|
1964
|
+
list = scope.in_effect_refinements(node)
|
|
1965
|
+
return nil if list.empty?
|
|
1966
|
+
|
|
1967
|
+
InEffectRefinements.record_refinement_names(node.name)
|
|
1968
|
+
return dynamic_top if refined_receiver?(receiver) && opaque_refinement_span?(node, list)
|
|
1969
|
+
return nil if scope.discovered_refinements.empty?
|
|
1970
|
+
|
|
1971
|
+
refined_indirect_result(node, receiver, list) ||
|
|
1972
|
+
refined_receiver_result(node, receiver, list, arg_types, block_type)
|
|
1973
|
+
end
|
|
1974
|
+
|
|
1975
|
+
# ADR-121 WD4 — `send` / `__send__` / `public_send` with a literal name, `respond_to?` and `method` /
|
|
1976
|
+
# `public_method` with one, and a `&:name` block argument honour refinements too (CRuby `test_refinement.rb`;
|
|
1977
|
+
# probed on Ruby 4.0.5). When the name they reach is refined in effect for the receiver they answer
|
|
1978
|
+
# `Dynamic[top]`: typing them through the arm is deferred, and `respond_to?` must never fold to `false`. A
|
|
1979
|
+
# `&:name` block's receiver is the yielded element, which is not known here, so any in-effect refinement of the
|
|
1980
|
+
# name answers. Only `methods` ignores refinements and keeps its answer.
|
|
1981
|
+
def refined_indirect_result(node, receiver, list)
|
|
1982
|
+
name = REFINED_NAMING_CALLS.include?(node.name) ? literal_method_name_argument(node) : nil
|
|
1983
|
+
return dynamic_top if name && refined_name_for?(receiver, name, list)
|
|
1984
|
+
|
|
1985
|
+
block_name = symbol_block_argument(node)
|
|
1986
|
+
dynamic_top if block_name && refined_anywhere?(block_name, list)
|
|
1987
|
+
end
|
|
1988
|
+
|
|
1989
|
+
REFINED_NAMING_CALLS = Set[:send, :__send__, :public_send, :respond_to?, :method, :public_method].freeze
|
|
1990
|
+
private_constant :REFINED_NAMING_CALLS
|
|
1991
|
+
|
|
1992
|
+
def literal_method_name_argument(node)
|
|
1993
|
+
argument = node.arguments&.arguments&.first
|
|
1994
|
+
name = argument.unescaped if argument.is_a?(Prism::SymbolNode) || argument.is_a?(Prism::StringNode)
|
|
1995
|
+
return nil if name.nil?
|
|
1996
|
+
|
|
1997
|
+
name = name.to_sym
|
|
1998
|
+
InEffectRefinements.record_refinement_names(name)
|
|
1999
|
+
name
|
|
2000
|
+
end
|
|
2001
|
+
|
|
2002
|
+
def symbol_block_argument(node)
|
|
2003
|
+
block = node.block
|
|
2004
|
+
return nil unless block.is_a?(Prism::BlockArgumentNode) && block.expression.is_a?(Prism::SymbolNode)
|
|
2005
|
+
|
|
2006
|
+
name = block.expression.unescaped.to_sym
|
|
2007
|
+
InEffectRefinements.record_refinement_names(name)
|
|
2008
|
+
name
|
|
2009
|
+
end
|
|
2010
|
+
|
|
2011
|
+
def refined_name_for?(receiver, name, list)
|
|
2012
|
+
members = receiver.is_a?(Type::Union) ? receiver.members : [receiver]
|
|
2013
|
+
members.any? do |member|
|
|
2014
|
+
next !RefinedDispatch.winner(scope, member.class_name, name, list, kind: :singleton).nil? if
|
|
2015
|
+
member.is_a?(Type::Singleton)
|
|
2016
|
+
|
|
2017
|
+
class_name = refined_receiver_class_name(member)
|
|
2018
|
+
!class_name.nil? && !RefinedDispatch.winner(scope, class_name, name, list).nil?
|
|
2019
|
+
end
|
|
2020
|
+
end
|
|
2021
|
+
|
|
2022
|
+
# ADR-121 WD7 — a names-wildcard row counts as a refinement of every name.
|
|
2023
|
+
def refined_anywhere?(name, list)
|
|
2024
|
+
refinements = scope.discovered_refinements
|
|
2025
|
+
wildcard = Scope::DiscoveryIndex::REFINEMENT_WILDCARD
|
|
2026
|
+
return true if list.include?(InEffectRefinements::UNKNOWN) &&
|
|
2027
|
+
refinements.any? { |_refined, methods| methods.key?(name) || methods.key?(wildcard) }
|
|
2028
|
+
|
|
2029
|
+
!RefinedDispatch.targets(refinements, name, list).nil? ||
|
|
2030
|
+
!RefinedDispatch.targets(refinements, wildcard, list).nil?
|
|
2031
|
+
end
|
|
2032
|
+
|
|
2033
|
+
# ADR-121 WD7 — does the call's lexical list (a plugin-declared module is the plugin's declaration and never
|
|
2034
|
+
# opaque) hold an opaque module? `list` is the full list, which is the lexical one when nothing is declared.
|
|
2035
|
+
def opaque_refinement_span?(node, list)
|
|
2036
|
+
lexical = scope.declared_refinements.empty? ? list : scope.in_effect_refinements(node, InEffectRefinements::EMPTY)
|
|
2037
|
+
InEffectRefinements.opaque_in?(scope, lexical)
|
|
2038
|
+
end
|
|
2039
|
+
|
|
2040
|
+
# Does `receiver` (or a member of a union) have a lookup the refined arm answers for: an instance of a class,
|
|
2041
|
+
# or a class object (issue #1752), not `Dynamic`?
|
|
2042
|
+
def refined_receiver?(receiver)
|
|
2043
|
+
members = receiver.is_a?(Type::Union) ? receiver.members : [receiver]
|
|
2044
|
+
members.any? { |member| member.is_a?(Type::Singleton) || !refined_receiver_class_name(member).nil? }
|
|
2045
|
+
end
|
|
2046
|
+
|
|
2047
|
+
def refined_receiver_result(node, receiver, list, arg_types, block_type)
|
|
2048
|
+
return refined_member_result(node, receiver, list, arg_types, block_type) unless receiver.is_a?(Type::Union)
|
|
2049
|
+
|
|
2050
|
+
answers = receiver.members.map { |member| refined_member_result(node, member, list, arg_types, block_type) }
|
|
2051
|
+
return nil if answers.none?
|
|
2052
|
+
|
|
2053
|
+
members = receiver.members.each_with_index.map do |member, index|
|
|
2054
|
+
answers[index] || call_result_type_for(node, receiver_override: member)
|
|
2055
|
+
end
|
|
2056
|
+
Type::Combinator.union(*members)
|
|
2057
|
+
end
|
|
2058
|
+
|
|
2059
|
+
def refined_member_result(node, receiver, list, arg_types, block_type)
|
|
2060
|
+
return refined_class_object_result(node, receiver, list, arg_types, block_type) if
|
|
2061
|
+
receiver.is_a?(Type::Singleton)
|
|
2062
|
+
|
|
2063
|
+
class_name = refined_receiver_class_name(receiver)
|
|
2064
|
+
return nil if class_name.nil?
|
|
2065
|
+
|
|
2066
|
+
winner = RefinedDispatch.winner(scope, class_name, node.name, list)
|
|
2067
|
+
return nil if winner.nil?
|
|
2068
|
+
return dynamic_top if winner == RefinedDispatch::UNKNOWN
|
|
2069
|
+
|
|
2070
|
+
refined_body_return(winner, node.name, Type::Combinator.nominal_of(class_name), arg_types, block_type) ||
|
|
2071
|
+
dynamic_top
|
|
2072
|
+
end
|
|
2073
|
+
|
|
2074
|
+
# Issue #1752 — a call on a class object, walked through its singleton levels. A refinement that wins answers
|
|
2075
|
+
# `Dynamic[top]`, never its refine body's type: a hook on any ancestor (an RBS-declared `include Singleton`, a
|
|
2076
|
+
# gem concern, `define_singleton_method(:included)`, a `Module#included` patch) can install the class object's
|
|
2077
|
+
# own method where no table sees it, and the body's type then fires downstream on correct code. A walk that finds
|
|
2078
|
+
# no winner keeps the class object's own answer (`Integer.sqrt(16)` stays `Integer`).
|
|
2079
|
+
def refined_class_object_result(node, receiver, list, _arg_types, _block_type)
|
|
2080
|
+
winner = RefinedDispatch.winner(scope, receiver.class_name, node.name, list, kind: :singleton)
|
|
2081
|
+
dynamic_top unless winner.nil?
|
|
2082
|
+
end
|
|
2083
|
+
|
|
2084
|
+
# The winning refine body's inferred return, re-typed with the receiver's class as `self` and the call's
|
|
2085
|
+
# argument types bound as an undeclared method's are; nil when the body is not readable here.
|
|
2086
|
+
#
|
|
2087
|
+
# A body another file wrote is re-typed with that file's in-effect refinements at the `def` (its own module
|
|
2088
|
+
# among them), so a sibling override it calls answers from its refine body too.
|
|
2089
|
+
def refined_body_return(winner, method_name, self_type, arg_types, block_type)
|
|
2090
|
+
def_node, foreign =
|
|
2091
|
+
InEffectRefinements.refinement_def_with_query(scope, winner.module_name, winner.refined_class, method_name)
|
|
2092
|
+
return nil if def_node.nil?
|
|
2093
|
+
|
|
2094
|
+
refinements = foreign&.at(def_node.location.start_offset) do |name|
|
|
2095
|
+
InEffectRefinements.activated_modules(scope, name)
|
|
2096
|
+
end
|
|
2097
|
+
infer_user_method_return(def_node, self_type, arg_types, yield_type: block_type, refinements: refinements)
|
|
2098
|
+
rescue StandardError
|
|
2099
|
+
nil
|
|
2100
|
+
end
|
|
2101
|
+
|
|
2102
|
+
# The class an instance-side receiver is an instance of, or nil for one this instance walk does not answer: a
|
|
2103
|
+
# class object (it walks its own lookup, {#refined_class_object_result}), `Dynamic`, `top`, or a shape with no
|
|
2104
|
+
# class.
|
|
2105
|
+
def refined_receiver_class_name(receiver)
|
|
2106
|
+
case receiver
|
|
2107
|
+
when Type::Nominal, Type::StructInstance, Type::DataInstance then receiver.class_name
|
|
2108
|
+
when Type::Constant then receiver.value.class.name
|
|
2109
|
+
when Type::Tuple then "Array"
|
|
2110
|
+
when Type::HashShape then "Hash"
|
|
2111
|
+
when Type::IntegerRange then "Integer"
|
|
2112
|
+
when Type::FloatRange then "Float"
|
|
2113
|
+
when Type::Refined, Type::Difference then refined_receiver_class_name(receiver.base)
|
|
2114
|
+
end
|
|
2115
|
+
end
|
|
2116
|
+
|
|
1930
2117
|
# The post-dispatch tiers for a call `MethodDispatcher` could not answer, in their historical
|
|
1931
2118
|
# order; extracted from {#call_dispatch_type_for} whole.
|
|
1932
2119
|
def dispatch_miss_result(node, receiver, arg_types, block_type = nil)
|
|
@@ -2284,7 +2471,7 @@ module Rigor
|
|
|
2284
2471
|
return every_definer_raises_type(raising, receiver, call_node, arg_types, block_type) if def_node.nil?
|
|
2285
2472
|
|
|
2286
2473
|
result = infer_user_method_return(def_node, receiver, arg_types,
|
|
2287
|
-
self_fold_safe: fold_safe_call_receiver?(call_node
|
|
2474
|
+
self_fold_safe: fold_safe_call_receiver?(call_node),
|
|
2288
2475
|
yield_type: block_type)
|
|
2289
2476
|
return result if result.nil?
|
|
2290
2477
|
|
|
@@ -2319,11 +2506,11 @@ module Rigor
|
|
|
2319
2506
|
# This is only a property of the receiver expression; whether the grant is actually issued also needs
|
|
2320
2507
|
# a `StructInstance` carrier and a body that survives the self-use scan, both decided in
|
|
2321
2508
|
# {#build_user_method_body_scope}.
|
|
2322
|
-
def fold_safe_call_receiver?(call_node
|
|
2509
|
+
def fold_safe_call_receiver?(call_node)
|
|
2323
2510
|
return false if call_node.nil?
|
|
2324
2511
|
|
|
2325
2512
|
case call_node.receiver
|
|
2326
|
-
when Prism::CallNode then materialization_call?(call_node.receiver
|
|
2513
|
+
when Prism::CallNode then materialization_call?(call_node.receiver)
|
|
2327
2514
|
when Prism::LocalVariableReadNode then scope.struct_fold_safe?(call_node.receiver.name)
|
|
2328
2515
|
when nil, Prism::SelfNode then scope.struct_fold_safe?(:self)
|
|
2329
2516
|
else false
|
|
@@ -2333,10 +2520,10 @@ module Rigor
|
|
|
2333
2520
|
# Issue #595 — the materialisation test lives ONCE, in
|
|
2334
2521
|
# [`struct_materialization.rb`](method_dispatcher/struct_materialization.rb), because the direct
|
|
2335
2522
|
# member-read gate ({MethodDispatcher::StructFolding}`#fresh_receiver?`) needs the identical answer;
|
|
2336
|
-
# see {MethodDispatcher::StructMaterialization.materialization_call?}.
|
|
2337
|
-
#
|
|
2338
|
-
def materialization_call?(node
|
|
2339
|
-
MethodDispatcher::StructMaterialization.materialization_call?(node,
|
|
2523
|
+
# see {MethodDispatcher::StructMaterialization.materialization_call?}. A `.with` call is never one
|
|
2524
|
+
# (`Struct` has no `#with`, #1335), so the test needs no lookup from either consumer.
|
|
2525
|
+
def materialization_call?(node)
|
|
2526
|
+
MethodDispatcher::StructMaterialization.materialization_call?(node, scope)
|
|
2340
2527
|
end
|
|
2341
2528
|
|
|
2342
2529
|
# Module-singleton call resolution (ADR-57 follow-up) — resolves `Foo.<name>` on a `Singleton[Foo]`
|
|
@@ -2505,7 +2692,7 @@ module Rigor
|
|
|
2505
2692
|
def every_definer_raises_type(defs, receiver, call_node, arg_types, block_type)
|
|
2506
2693
|
return nil if defs.nil?
|
|
2507
2694
|
|
|
2508
|
-
self_fold_safe = fold_safe_call_receiver?(call_node
|
|
2695
|
+
self_fold_safe = fold_safe_call_receiver?(call_node)
|
|
2509
2696
|
defs.each do |def_node|
|
|
2510
2697
|
result = infer_user_method_return(def_node, receiver, arg_types,
|
|
2511
2698
|
self_fold_safe: self_fold_safe, yield_type: block_type)
|
|
@@ -2959,14 +3146,37 @@ module Rigor
|
|
|
2959
3146
|
# not observable on the body scope, so it does need its own memo-key slot. It is dropped for a def
|
|
2960
3147
|
# that cannot reach a `yield` — nearly all of them — which keeps the key shape constant for the
|
|
2961
3148
|
# methods the memo actually carries and confines the extra dimension to yielding callees.
|
|
2962
|
-
|
|
3149
|
+
# Issue #1703 — inside the guarded re-walk a callee's return summary is still computed with `key?` guards off
|
|
3150
|
+
# ({KeyPresenceGuard.without_guards}): summaries are memoised for the whole run, and one computed with a guard
|
|
3151
|
+
# would reach the analysis every rule reads.
|
|
3152
|
+
#
|
|
3153
|
+
# Issue #1664 — `refinements` are the modules in effect in a refine body another file wrote, which the body scope
|
|
3154
|
+
# carries as declared refinements: its nodes are not this file's, so this file's lexical list cannot answer them.
|
|
3155
|
+
# The return memo and the recursion guard do not key on them, which holds because such a node comes only from
|
|
3156
|
+
# `DefNodeResolver.refinement_query`'s own parse and always carries that file's one list; a caller that types
|
|
3157
|
+
# the same node under another list must add it to the key.
|
|
3158
|
+
def infer_user_method_return(def_node, receiver, arg_types, self_fold_safe: false, yield_type: nil,
|
|
3159
|
+
refinements: nil)
|
|
2963
3160
|
return nil if def_node.body.nil?
|
|
3161
|
+
unless KeyPresenceGuard.active?
|
|
3162
|
+
return infer_user_method_return_unguarded(def_node, receiver, arg_types, self_fold_safe, yield_type,
|
|
3163
|
+
refinements)
|
|
3164
|
+
end
|
|
3165
|
+
|
|
3166
|
+
KeyPresenceGuard.without_guards do
|
|
3167
|
+
infer_user_method_return_unguarded(def_node, receiver, arg_types, self_fold_safe, yield_type, refinements)
|
|
3168
|
+
end
|
|
3169
|
+
end
|
|
2964
3170
|
|
|
3171
|
+
def infer_user_method_return_unguarded(def_node, receiver, arg_types, self_fold_safe, yield_type,
|
|
3172
|
+
refinements = nil)
|
|
2965
3173
|
yield_type = nil unless yield_type && body_yields?(def_node)
|
|
2966
3174
|
body_scope = build_user_method_body_scope(def_node, receiver, arg_types,
|
|
2967
3175
|
self_fold_safe: self_fold_safe)
|
|
2968
3176
|
return nil if body_scope.nil?
|
|
2969
3177
|
|
|
3178
|
+
body_scope = body_scope.with_declared_refinements(refinements) if refinements
|
|
3179
|
+
|
|
2970
3180
|
# Recursion-guard signature. Keyed on `(receiver, method)` only — NOT the argument types. ADR-24 WD5:
|
|
2971
3181
|
# a method whose summary is still being computed resolves to `Dynamic[top]` for that cycle. Keying on
|
|
2972
3182
|
# arg types would let mutual recursion through a `module_function` module (`Acceptance#accepts` →
|
|
@@ -4021,12 +4231,13 @@ module Rigor
|
|
|
4021
4231
|
method_name: call_node.name,
|
|
4022
4232
|
arg_types: arg_types,
|
|
4023
4233
|
environment: scope.environment,
|
|
4024
|
-
scope: scope
|
|
4234
|
+
scope: scope,
|
|
4235
|
+
call_node: call_node
|
|
4025
4236
|
)
|
|
4026
4237
|
block_return_for(
|
|
4027
4238
|
block_arg, expected,
|
|
4028
4239
|
call_node: call_node,
|
|
4029
|
-
|
|
4240
|
+
narrowing: block_body_self_narrowing(call_node, receiver_type),
|
|
4030
4241
|
repeats: !BLOCK_VALUE_DISCARDING.include?(call_node.name) && block_may_repeat?(call_node, receiver_type)
|
|
4031
4242
|
)
|
|
4032
4243
|
rescue StandardError
|
|
@@ -4093,7 +4304,7 @@ module Rigor
|
|
|
4093
4304
|
block_scope = block_entry_scope(
|
|
4094
4305
|
block_node, param_types,
|
|
4095
4306
|
call_node: call_node,
|
|
4096
|
-
|
|
4307
|
+
narrowing: block_body_self_narrowing(call_node, receiver),
|
|
4097
4308
|
captured: repeating_captured_bindings(block_node, param_types, block_may_repeat?(call_node, receiver))
|
|
4098
4309
|
)
|
|
4099
4310
|
_result, collected = StatementEvaluator.with_break_value_sink do
|
|
@@ -4102,10 +4313,12 @@ module Rigor
|
|
|
4102
4313
|
collected.filter_map { |jump, type| type if targets.key?(jump) }
|
|
4103
4314
|
end
|
|
4104
4315
|
|
|
4105
|
-
# The block body's narrowed `self_type
|
|
4316
|
+
# The block body's narrowing — a {MacroBlockSelfType::Match} carrying the narrowed `self_type` and the declared
|
|
4317
|
+
# refinements — or `nil` to leave the scope contract unchanged.
|
|
4106
4318
|
#
|
|
4107
4319
|
# ADR-16 Tier A: a registered plugin's `block_as_methods` entry matching `(receiver_type,
|
|
4108
|
-
# call_node.name)` narrows to the receiver class's instance type
|
|
4320
|
+
# call_node.name)` narrows to the receiver class's instance type, and puts its `refinements:` in effect in the
|
|
4321
|
+
# body (issue #1667).
|
|
4109
4322
|
#
|
|
4110
4323
|
# Issue #963: `define_method(:name) { ... }` installs its block as an instance method and runs it with
|
|
4111
4324
|
# `self` bound to the receiving instance, so the block body's `self` is the INSTANCE side of a class body's
|
|
@@ -4115,9 +4328,11 @@ module Rigor
|
|
|
4115
4328
|
# matters wherever the block's value is observable, e.g. a project-declared generic `define_method`
|
|
4116
4329
|
# signature that returns the block's own type.
|
|
4117
4330
|
def block_body_self_narrowing(call_node, receiver_type)
|
|
4118
|
-
MacroBlockSelfType.
|
|
4119
|
-
|
|
4120
|
-
|
|
4331
|
+
match = MacroBlockSelfType.match_for(scope: scope, call_node: call_node, receiver_type: receiver_type)
|
|
4332
|
+
return match if match
|
|
4333
|
+
|
|
4334
|
+
defined = DefineMethodBlockSelf.narrow_self_type_for(scope: scope, call_node: call_node)
|
|
4335
|
+
defined && MacroBlockSelfType::Match.new(self_type: defined, refinements: InEffectRefinements::EMPTY)
|
|
4121
4336
|
end
|
|
4122
4337
|
|
|
4123
4338
|
def break_arm_param_types(call_node, receiver)
|
|
@@ -4126,16 +4341,17 @@ module Rigor
|
|
|
4126
4341
|
method_name: call_node.name,
|
|
4127
4342
|
arg_types: call_arg_types(call_node),
|
|
4128
4343
|
environment: scope.environment,
|
|
4129
|
-
scope: scope
|
|
4344
|
+
scope: scope,
|
|
4345
|
+
call_node: call_node
|
|
4130
4346
|
)
|
|
4131
4347
|
end
|
|
4132
4348
|
|
|
4133
|
-
def block_return_for(block_arg, expected, call_node: nil,
|
|
4349
|
+
def block_return_for(block_arg, expected, call_node: nil, narrowing: nil, repeats: false)
|
|
4134
4350
|
case block_arg
|
|
4135
4351
|
when Prism::BlockNode
|
|
4136
4352
|
captured = repeating_captured_bindings(block_arg, expected, repeats)
|
|
4137
4353
|
entry = block_entry_scope(
|
|
4138
|
-
block_arg, expected, call_node: call_node,
|
|
4354
|
+
block_arg, expected, call_node: call_node, narrowing: narrowing, captured: captured
|
|
4139
4355
|
)
|
|
4140
4356
|
type_block_body(block_arg, entry, captured: captured)
|
|
4141
4357
|
when Prism::BlockArgumentNode
|
|
@@ -4151,13 +4367,14 @@ module Rigor
|
|
|
4151
4367
|
# Issue #316 — mirrors `StatementEvaluator#build_block_entry_scope`: the block body's `self` is the
|
|
4152
4368
|
# yielding method's business, so the return-typing pass must see the same unmodelled-self mark. Issue #1358
|
|
4153
4369
|
# — and the same match-global view ({MatchRebinding.block_entry}), which reads the owning `call_node`.
|
|
4154
|
-
def block_entry_scope(block_node, expected, call_node: nil,
|
|
4370
|
+
def block_entry_scope(block_node, expected, call_node: nil, narrowing: nil, captured: nil)
|
|
4155
4371
|
entry = MatchRebinding.block_entry(scope.entering_opaque_block, block_node, call_node)
|
|
4156
4372
|
entry = captured.lay(entry) if captured
|
|
4157
4373
|
block_scope = BlockParameterBinder.new(expected_param_types: expected).bind_onto(block_node, entry)
|
|
4158
|
-
return block_scope unless
|
|
4374
|
+
return block_scope unless narrowing
|
|
4159
4375
|
|
|
4160
|
-
|
|
4376
|
+
keeps_unknown = call_node ? scope.block_self_narrowing_unknown?(call_node) : false
|
|
4377
|
+
MacroBlockSelfType.apply(block_scope, narrowing, keeps_unknown: keeps_unknown)
|
|
4161
4378
|
end
|
|
4162
4379
|
|
|
4163
4380
|
# The #587 (b) captured binding when the call may run the block more than once ({#block_may_repeat?}), and
|
|
@@ -5218,13 +5435,27 @@ module Rigor
|
|
|
5218
5435
|
acc, rest = inject_constant_start(members, seed, has_seed)
|
|
5219
5436
|
return nil if acc.nil?
|
|
5220
5437
|
|
|
5221
|
-
rest.
|
|
5222
|
-
|
|
5438
|
+
captured = inject_captured_bindings(block, rest.map { |value| Type::Combinator.constant_of(value) })
|
|
5439
|
+
rest.each_with_index do |element_value, position|
|
|
5440
|
+
acc = inject_constant_step(block, acc, element_value, captured, position)
|
|
5223
5441
|
return nil if acc.nil?
|
|
5224
5442
|
end
|
|
5225
5443
|
acc
|
|
5226
5444
|
end
|
|
5227
5445
|
|
|
5446
|
+
# Issue #1332 — the #587 (b) captured-block binding ({#captured_block_bindings}) both inject arms lay under
|
|
5447
|
+
# every step, as the Tuple / Range per-element folds and the per-pair `HashShape` fold do. Without it each
|
|
5448
|
+
# step reads a captured variable at its entry binding, so a body that rebinds or mutates one (`n += 1`,
|
|
5449
|
+
# `log << e`, `cache[:k] ||= e`) pinned the result: `n = 0; [1, 2].inject(0) { |a, e| r = a + n; n += 1;
|
|
5450
|
+
# r }` folded to `0` where Ruby answers `1`. The fixpoint binds the accumulator parameter to `Dynamic[top]`
|
|
5451
|
+
# rather than to a type the inject fixpoint has not converged yet: a captured name that reads the
|
|
5452
|
+
# accumulator widens rather than pins. `element_types` is the constant arm's steps, one position per block
|
|
5453
|
+
# run, which {RepeatedOrWrites} reads; nil for the nominal arm, which marks every repeated site.
|
|
5454
|
+
def inject_captured_bindings(block, element_types, element: nil)
|
|
5455
|
+
element ||= Type::Combinator.union(*element_types)
|
|
5456
|
+
captured_block_bindings(block, [Type::Combinator.untyped, element], element_types: element_types)
|
|
5457
|
+
end
|
|
5458
|
+
|
|
5228
5459
|
# Extracts the receiver's foldable constant values, size-capped before enumeration, or nil to decline.
|
|
5229
5460
|
def inject_constant_members(receiver)
|
|
5230
5461
|
case receiver
|
|
@@ -5276,9 +5507,9 @@ module Rigor
|
|
|
5276
5507
|
# Evaluates the block body once with the running constant accumulator + the next constant element
|
|
5277
5508
|
# bound to the block params, returning the result when it is a foldable `Constant` within the
|
|
5278
5509
|
# magnitude cap, else nil to decline the whole fold.
|
|
5279
|
-
def inject_constant_step(block, acc, element_value)
|
|
5510
|
+
def inject_constant_step(block, acc, element_value, captured, position)
|
|
5280
5511
|
element = Type::Combinator.constant_of(element_value)
|
|
5281
|
-
result = type_block_body_with_param(block, [acc, element])
|
|
5512
|
+
result = type_block_body_with_param(block, [acc, element], captured: captured, position: position)
|
|
5282
5513
|
return nil unless result.is_a?(Type::Constant)
|
|
5283
5514
|
return nil unless inject_foldable?(result.value)
|
|
5284
5515
|
return nil if inject_magnitude_too_large?(result.value)
|
|
@@ -5308,13 +5539,14 @@ module Rigor
|
|
|
5308
5539
|
seed_acc = has_seed ? seed : element
|
|
5309
5540
|
return nil if seed_acc.nil?
|
|
5310
5541
|
|
|
5542
|
+
captured = inject_captured_bindings(block, nil, element: element)
|
|
5311
5543
|
converged = BodyFixpoint.converge(
|
|
5312
5544
|
names: [:__inject_acc__],
|
|
5313
5545
|
seed_bindings: { __inject_acc__: seed_acc },
|
|
5314
5546
|
widen: method(:widen_value_pinned),
|
|
5315
5547
|
evaluate_body: lambda do |bindings|
|
|
5316
5548
|
acc = bindings[:__inject_acc__]
|
|
5317
|
-
result = type_block_body_with_param(block, [acc, element])
|
|
5549
|
+
result = type_block_body_with_param(block, [acc, element], captured: captured)
|
|
5318
5550
|
result.nil? ? {} : { __inject_acc__: result }
|
|
5319
5551
|
end
|
|
5320
5552
|
)
|
|
@@ -82,7 +82,7 @@ module Rigor
|
|
|
82
82
|
|
|
83
83
|
# A standalone walk, for a file no existing descent visits (a `pre_eval:` entry).
|
|
84
84
|
def scan(root)
|
|
85
|
-
collector = Collector.new
|
|
85
|
+
collector = Collector.new(InEffectRefinements.new(root))
|
|
86
86
|
walk(root, collector, true)
|
|
87
87
|
collector.census.freeze
|
|
88
88
|
end
|
|
@@ -98,13 +98,16 @@ module Rigor
|
|
|
98
98
|
|
|
99
99
|
# Accumulates one file's census, fed each node of a pre-order descent. `top_level` says whether the node sits
|
|
100
100
|
# outside every `class` / `module` body; it is the host walk's to track. `refine` blocks are remembered by
|
|
101
|
-
# offset as they are met, so a node inside one is known without the host walk tracking it.
|
|
101
|
+
# offset as they are met, so a node inside one is known without the host walk tracking it. `in_effect` is the
|
|
102
|
+
# file's {InEffectRefinements}: a `refine` it places where `self` is a class is the class's own method, so its
|
|
103
|
+
# block is not a refinement (issue #1689).
|
|
102
104
|
class Collector
|
|
103
105
|
attr_reader :census
|
|
104
106
|
|
|
105
|
-
def initialize
|
|
107
|
+
def initialize(in_effect)
|
|
106
108
|
@census = Set.new
|
|
107
109
|
@refine_ranges = []
|
|
110
|
+
@in_effect = in_effect
|
|
108
111
|
end
|
|
109
112
|
|
|
110
113
|
# A node the collector cannot read must not fail the analysis of its file, nor of every file the pre-pass
|
|
@@ -223,7 +226,7 @@ module Rigor
|
|
|
223
226
|
def self_receiver?(node) = node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
|
|
224
227
|
|
|
225
228
|
def remember_refine(node)
|
|
226
|
-
return unless
|
|
229
|
+
return unless ScopeIndexer.refine_call?(node) && !@in_effect.class_body_refine?(node)
|
|
227
230
|
|
|
228
231
|
location = node.block.location
|
|
229
232
|
@refine_ranges << (location.start_offset...location.end_offset)
|
|
@@ -406,7 +406,7 @@ module Rigor
|
|
|
406
406
|
arguments = call_node.arguments&.arguments || []
|
|
407
407
|
MethodDispatcher.expected_block_param_types(
|
|
408
408
|
receiver_type: receiver, method_name: call_node.name, environment: scope.environment, scope: scope,
|
|
409
|
-
arg_types: arguments.map { |argument| scope.type_of(argument) }
|
|
409
|
+
arg_types: arguments.map { |argument| scope.type_of(argument) }, call_node: call_node
|
|
410
410
|
)
|
|
411
411
|
rescue StandardError
|
|
412
412
|
[]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
class InEffectRefinements
|
|
8
|
+
# Issue #1666 — the Proc literals whose body a `.refined` receiver call applies to, recognised from syntax.
|
|
9
|
+
module ProcLiterals
|
|
10
|
+
# The `Kernel` calls whose literal block is the Proc they return.
|
|
11
|
+
BARE_PROC_CALLS = %i[proc lambda].freeze
|
|
12
|
+
private_constant :BARE_PROC_CALLS
|
|
13
|
+
|
|
14
|
+
# Copies that keep a refined Proc's refinements (`test_refined_preserved_by_dup`): a chain reads through them.
|
|
15
|
+
COPY_CALLS = %i[dup clone].freeze
|
|
16
|
+
private_constant :COPY_CALLS
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# `[literal, refined calls]` for the outermost `.refined` call `node`: the Proc literal the chain starts from
|
|
21
|
+
# ({.refinable}, or nil), and every `.refined` call of the chain, innermost (first called) first. The chain
|
|
22
|
+
# reads through parentheses and through `dup` / `clone`.
|
|
23
|
+
def refined_chain(node)
|
|
24
|
+
calls = [node]
|
|
25
|
+
receiver = unwrap(node.receiver)
|
|
26
|
+
loop do
|
|
27
|
+
if refined_call?(receiver)
|
|
28
|
+
calls << receiver
|
|
29
|
+
elsif !copy_call?(receiver)
|
|
30
|
+
break
|
|
31
|
+
end
|
|
32
|
+
receiver = unwrap(receiver.receiver)
|
|
33
|
+
end
|
|
34
|
+
[refinable(receiver), calls.reverse]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def refined_call?(node) = node.is_a?(Prism::CallNode) && node.name == :refined
|
|
38
|
+
|
|
39
|
+
def copy_call?(node)
|
|
40
|
+
node.is_a?(Prism::CallNode) && COPY_CALLS.include?(node.name) && node.arguments.nil? && node.block.nil? &&
|
|
41
|
+
!node.receiver.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# `( expr )` with one statement reads as `expr`.
|
|
45
|
+
def unwrap(node)
|
|
46
|
+
while node.is_a?(Prism::ParenthesesNode) && node.body.is_a?(Prism::StatementsNode) &&
|
|
47
|
+
node.body.body.size == 1
|
|
48
|
+
node = node.body.body.first
|
|
49
|
+
end
|
|
50
|
+
node
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The node whose span is the body `node.refined(…)` applies to: `node` itself for a lambda literal, or the
|
|
54
|
+
# literal block of a `proc` / `lambda` call on no receiver or on `Kernel`, or of `Proc.new`; nil for anything
|
|
55
|
+
# else. A Proc held in a variable, or one passed as `&blk`, is not one: which block it holds is not syntax.
|
|
56
|
+
def refinable(node)
|
|
57
|
+
case node
|
|
58
|
+
when Prism::LambdaNode then node
|
|
59
|
+
when Prism::CallNode
|
|
60
|
+
block = node.block
|
|
61
|
+
return nil unless block.is_a?(Prism::BlockNode)
|
|
62
|
+
|
|
63
|
+
block if bare_proc_call?(node) || proc_new_call?(node)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def bare_proc_call?(node)
|
|
68
|
+
BARE_PROC_CALLS.include?(node.name) && (node.receiver.nil? || top_constant?(node.receiver, :Kernel))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def proc_new_call?(node)
|
|
72
|
+
node.name == :new && top_constant?(node.receiver, :Proc)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# `Name` or `::Name`.
|
|
76
|
+
def top_constant?(node, name)
|
|
77
|
+
(node.is_a?(Prism::ConstantReadNode) || (node.is_a?(Prism::ConstantPathNode) && node.parent.nil?)) &&
|
|
78
|
+
node.name == name
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
class InEffectRefinements
|
|
6
|
+
# Issue #1664 — the `def`s one file's refine bodies define, keyed by refining module, refined class (each name
|
|
7
|
+
# the `refine` argument's spelling can denote) and method, with the `Module.nesting` each is written in. The
|
|
8
|
+
# last `def` of a name wins, as Ruby's method table keeps it.
|
|
9
|
+
class RefineDefs
|
|
10
|
+
# ADR-121 WD7 — what {#lookup} answers for a name whose last definer has no body (`define_method`, `attr_*`,
|
|
11
|
+
# `undef`): the search for a body ends there, so the typed arm answers `Dynamic[top]` rather than another
|
|
12
|
+
# file's `def`.
|
|
13
|
+
BODILESS = :bodiless
|
|
14
|
+
# Issue #1749 — what {#lookup} answers for a name whose refinement the walk cannot attribute: one from a
|
|
15
|
+
# `refine` that runs inside a method (it exists once the method has run, replacing what a refine body wrote for
|
|
16
|
+
# the name, or adding nothing if it never runs, and `using` sees it only if it runs first), or from an eval on
|
|
17
|
+
# a constant that several names denote (it runs on one of them, which another file decides, and replaces or is
|
|
18
|
+
# replaced by a `def` of the name in a refine body of the module, whichever runs first). A `def` of the name
|
|
19
|
+
# in a module-body refine, in this file or another, cannot say which one answers, so the name is never typed
|
|
20
|
+
# from any body: the lookup ends there across every file that declares the module, and unlike {BODILESS} a
|
|
21
|
+
# later record does not replace it.
|
|
22
|
+
UNDECIDED = :undecided
|
|
23
|
+
|
|
24
|
+
attr_reader :nestings
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@nodes = {}
|
|
28
|
+
@nestings = {}.compare_by_identity
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# `owners` are the refining modules (several where an eval on a constant may run on any of its names), each
|
|
32
|
+
# filed under every class of `targets`. `name` is the method `def_node` answers for: its own name, or an
|
|
33
|
+
# alias's (ADR-121 WD7). A nil `def_node` is a later definer with no body (`define_method`, `attr_*`, `undef`),
|
|
34
|
+
# which replaces the earlier `def`. A name whose refinement the walk cannot attribute keeps no body and is
|
|
35
|
+
# {UNDECIDED}: several owners (Ruby runs the body on one of them only), or `created`, true for a `refine` that
|
|
36
|
+
# runs inside a method.
|
|
37
|
+
def record(owners, targets, name, def_node, nesting, created: false)
|
|
38
|
+
undecided = created || owners.size > 1
|
|
39
|
+
def_node = nil if undecided
|
|
40
|
+
owners.each do |owner|
|
|
41
|
+
by_class = (@nodes[owner] ||= {})
|
|
42
|
+
targets.each do |class_name|
|
|
43
|
+
by_name = (by_class[class_name] ||= {})
|
|
44
|
+
by_name[name] = undecided ? UNDECIDED : (def_node || BODILESS) unless by_name[name] == UNDECIDED
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
@nestings[def_node] = nesting if def_node
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def lookup(module_name, class_name, method_name) = @nodes.dig(module_name, class_name, method_name)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|