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
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
# ADR-121 WD2 (issue #1664) — which in-effect refinement, if any, answers a call on an instance of a class. The
|
|
6
|
+
# typed refined arm (`ExpressionTyper#try_refined_dispatch`) asks it before every other answer, because those
|
|
7
|
+
# answer from the method a refinement replaces.
|
|
8
|
+
#
|
|
9
|
+
# Precedence follows `doc/syntax/refinements.rdoc` § Method Lookup: walk the receiver's classes from the most
|
|
10
|
+
# derived. At each class, the latest in-effect module refining the name for that class wins; otherwise its
|
|
11
|
+
# prepended modules, its own method and its included modules answer in that order, and the refinement
|
|
12
|
+
# declines; otherwise move on. A refinement of a module is decided where that module sits in the walk.
|
|
13
|
+
#
|
|
14
|
+
# The walk reads RBS ancestors for a class RBS knows (each ancestor a level of its own, so a core class's
|
|
15
|
+
# prepends are not told apart), and the project's `Scope::ResolutionChain` levels for one only the project
|
|
16
|
+
# declares, splicing in the RBS ancestors of the superclass it does not declare. "Defines the method" is an RBS
|
|
17
|
+
# declaration on that class itself, or a project `def`, `attr_*` or `define_method` of it. Core RBS sometimes
|
|
18
|
+
# redeclares an inherited method on a subclass, which stops the walk there: a known imprecision, in the
|
|
19
|
+
# declining direction.
|
|
20
|
+
#
|
|
21
|
+
# Issue #1740 — {OwnMethod} shares {.levels} with the walk below and asks the opposite question for the two
|
|
22
|
+
# argument checks: does the receiver's own method answer ahead of every refinement in effect?
|
|
23
|
+
#
|
|
24
|
+
# Issue #1752 — a class object is walked through its own lookup ({ClassObject}): its singleton class and each
|
|
25
|
+
# superclass's, then `Class`, `Module`, `Object`, `Kernel` and `BasicObject`, so a refinement of `Object` answers
|
|
26
|
+
# `Integer.oo` while `Integer.sqrt` keeps answering ahead of one.
|
|
27
|
+
module RefinedDispatch
|
|
28
|
+
# The refinement that answers: `module_name` refines `refined_class` (a class or module on the walk).
|
|
29
|
+
Winner = Data.define(:module_name, :refined_class)
|
|
30
|
+
|
|
31
|
+
# Issue #1752 — the singleton class `#<Class:of>` as an entry of a class object's walk ({ClassObject}): it
|
|
32
|
+
# defines the name through `of`'s own singleton methods, and no refinement table key names it.
|
|
33
|
+
SingletonClass = Data.define(:of)
|
|
34
|
+
|
|
35
|
+
# The answer when the in-effect list carries {InEffectRefinements::UNKNOWN} and some refinement defines the
|
|
36
|
+
# name on a class the walk reaches: any of them may answer.
|
|
37
|
+
UNKNOWN = :unknown
|
|
38
|
+
|
|
39
|
+
NO_TARGETS = {}.freeze
|
|
40
|
+
private_constant :NO_TARGETS
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
# `Scope::DiscoveryIndex::REFINEMENT_WILDCARD`, read when asked: this file loads before `Scope`.
|
|
45
|
+
def wildcard = Scope::DiscoveryIndex::REFINEMENT_WILDCARD
|
|
46
|
+
|
|
47
|
+
# A {Winner}, {UNKNOWN}, or nil when no in-effect refinement answers `method_name` on an instance of
|
|
48
|
+
# `class_name` (the class's own lookup does), or with `kind: :singleton` on the class object `class_name`
|
|
49
|
+
# itself (issue #1752). `list` is the call site's in-effect refinements.
|
|
50
|
+
#
|
|
51
|
+
# ADR-121 WD7 — a row the walk could not read never yields a {Winner}: an in-effect module's row for the name
|
|
52
|
+
# whose class the walk could not name reaches every receiver, and a names-wildcard row of a level the walk
|
|
53
|
+
# reaches before a definer may define the name, so both answer {UNKNOWN}. So does a winner whose module shares
|
|
54
|
+
# its last segment with another listed module (A1): which of a `using`'s declared candidates Ruby finds can
|
|
55
|
+
# depend on load order. A class object's walk always ends at `Object`, so where it cannot be read a row that
|
|
56
|
+
# may sit on it answers {UNKNOWN} too.
|
|
57
|
+
def winner(scope, class_name, method_name, list, kind: :instance)
|
|
58
|
+
refinements = scope.discovered_refinements
|
|
59
|
+
return UNKNOWN if class_unknown_row?(scope, refinements, method_name, list)
|
|
60
|
+
|
|
61
|
+
targets = resolved(scope, targets(refinements, method_name, list), method_name)
|
|
62
|
+
unread = targets(refinements, wildcard, list)
|
|
63
|
+
unknown = list.include?(InEffectRefinements::UNKNOWN)
|
|
64
|
+
return nil if targets.nil? && unread.nil? && !unknown
|
|
65
|
+
|
|
66
|
+
levels = levels(scope, class_name, kind: kind)
|
|
67
|
+
return unread_class_object(scope, refinements, method_name, list) if levels.nil? && kind == :singleton
|
|
68
|
+
return nil if levels.nil?
|
|
69
|
+
return UNKNOWN if unknown && unknown_reaches?(scope, refinements, method_name, levels)
|
|
70
|
+
|
|
71
|
+
walk_targets(scope, levels, method_name, [targets, unread], list)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# {UNKNOWN} when some row for `method_name` (or a names-wildcard row) that `list` puts in effect, or any row
|
|
75
|
+
# under the unknown marker, names a class or module a class object may reach ({ClassObject.reachable?}).
|
|
76
|
+
def unread_class_object(scope, refinements, method_name, list)
|
|
77
|
+
unknown = list.include?(InEffectRefinements::UNKNOWN)
|
|
78
|
+
reaches = refinements.any? do |refined, methods|
|
|
79
|
+
rows = [methods[method_name], methods[wildcard]].compact
|
|
80
|
+
!rows.empty? && (unknown || rows.any? { |modules| in_effect?(modules, list) }) &&
|
|
81
|
+
ClassObject.reachable?(scope, refined)
|
|
82
|
+
end
|
|
83
|
+
reaches ? UNKNOWN : nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def walk_targets(scope, levels, method_name, (targets, unread), list)
|
|
87
|
+
return nil if targets.nil? && unread.nil?
|
|
88
|
+
|
|
89
|
+
answer = walk(scope, levels, method_name, targets || NO_TARGETS, unread || NO_TARGETS)
|
|
90
|
+
answer.is_a?(Winner) && rival_spelling?(answer.module_name, list) ? UNKNOWN : answer
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# ADR-121 WD7 (A2) — does an in-effect module have a row for `method_name` (or a names-wildcard row) whose
|
|
94
|
+
# class the walk could not name ({InEffectRefinements.class_wildcard_key?})? It may refine the receiver's class.
|
|
95
|
+
def class_unknown_row?(scope, refinements, method_name, list)
|
|
96
|
+
refinements.any? do |refined, methods|
|
|
97
|
+
rows = [methods[method_name], methods[wildcard]].compact
|
|
98
|
+
!rows.empty? && rows.any? { |modules| in_effect?(modules, list) } &&
|
|
99
|
+
InEffectRefinements.class_wildcard_key?(scope, refined)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Is one of `modules` (a row's refining modules) in `list`? The wildcard module, which the walk could not name,
|
|
104
|
+
# is any listed module.
|
|
105
|
+
def in_effect?(modules, list)
|
|
106
|
+
return list.any? { |name| name != InEffectRefinements::UNKNOWN } if modules.include?(wildcard)
|
|
107
|
+
|
|
108
|
+
modules.any? { |name| list.include?(name) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def rival_spelling?(module_name, list)
|
|
112
|
+
segment = module_name.split("::").last
|
|
113
|
+
list.any? { |other| other.is_a?(String) && other != module_name && other.split("::").last == segment }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# `{refined class => the latest in-effect module refining method_name for it}`, or nil when none is in effect.
|
|
117
|
+
def targets(refinements, method_name, list)
|
|
118
|
+
out = nil
|
|
119
|
+
refinements.each do |refined, methods|
|
|
120
|
+
modules = methods[method_name]
|
|
121
|
+
next if modules.nil?
|
|
122
|
+
|
|
123
|
+
best = latest(modules, list)
|
|
124
|
+
(out ||= {})[refined] = best if best
|
|
125
|
+
end
|
|
126
|
+
out
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# The refinement table records every name a `refine` argument's spelling can denote (`refine String` inside
|
|
130
|
+
# `module M` records `M::String` and `String`), which only withholds a check. To type, a key must be the class
|
|
131
|
+
# Ruby's lexical lookup resolves: one the project or RBS knows, with no longer spelling of the same last segment
|
|
132
|
+
# that the same module refines the name for and that is known too (that one is innermost, so it wins). Nil
|
|
133
|
+
# when no key survives.
|
|
134
|
+
def resolved(scope, targets, method_name)
|
|
135
|
+
return nil if targets.nil?
|
|
136
|
+
|
|
137
|
+
refinements = scope.discovered_refinements
|
|
138
|
+
kept = targets.select do |refined, module_name|
|
|
139
|
+
known_class?(scope, refined) && !shadowed?(scope, refinements, refined, module_name, method_name)
|
|
140
|
+
end
|
|
141
|
+
kept.empty? ? nil : kept
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def shadowed?(scope, refinements, refined, module_name, method_name)
|
|
145
|
+
suffix = "::#{refined.split('::').last}"
|
|
146
|
+
depth = refined.count(":")
|
|
147
|
+
refinements.any? do |other, methods|
|
|
148
|
+
other.count(":") > depth && other.end_with?(suffix) && methods[method_name]&.include?(module_name) &&
|
|
149
|
+
known_class?(scope, other)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def known_class?(scope, name)
|
|
154
|
+
scope.discovered_classes.key?(name) || scope.environment&.class_known?(name) || false
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def latest(modules, list)
|
|
158
|
+
best = nil
|
|
159
|
+
best_index = -1
|
|
160
|
+
modules.each do |module_name|
|
|
161
|
+
index = list.index(module_name)
|
|
162
|
+
next unless index && index > best_index
|
|
163
|
+
|
|
164
|
+
best = module_name
|
|
165
|
+
best_index = index
|
|
166
|
+
end
|
|
167
|
+
best
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# ADR-121 WD7 — a names-wildcard row counts as a row for every name, and a row whose class the walk could not
|
|
171
|
+
# name reaches every level.
|
|
172
|
+
def unknown_reaches?(scope, refinements, method_name, levels)
|
|
173
|
+
unread_class = refinements.any? do |refined, methods|
|
|
174
|
+
(methods.key?(method_name) || methods.key?(wildcard)) &&
|
|
175
|
+
InEffectRefinements.class_wildcard_key?(scope, refined)
|
|
176
|
+
end
|
|
177
|
+
return true if unread_class
|
|
178
|
+
|
|
179
|
+
levels.any? do |_level_class, entries|
|
|
180
|
+
entries.any? do |entry|
|
|
181
|
+
methods = refinements[entry]
|
|
182
|
+
!methods.nil? && (methods.key?(method_name) || methods.key?(wildcard))
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# A level the project mixes a module into whose position RBS does not record (`mixed`) is decided by its own
|
|
188
|
+
# refinement first, as any level is; past that, the mixin may define the name, so a refinement further up the
|
|
189
|
+
# walk may or may not win and the answer is {UNKNOWN}, never the replaced method's. ADR-121 WD7: a level an
|
|
190
|
+
# in-effect module has a names-wildcard row for (`unread`) may define the name too, so it answers {UNKNOWN}.
|
|
191
|
+
#
|
|
192
|
+
# Issue #1752 — on a class object's walk a singleton level that only an entry no table names may answer
|
|
193
|
+
# (`:continue_unsure`) makes a refinement reached past it {UNKNOWN}: that entry may define the name.
|
|
194
|
+
def walk(scope, levels, method_name, targets, unread)
|
|
195
|
+
unsure = false
|
|
196
|
+
levels.each_index do |index|
|
|
197
|
+
answer = level_answer(scope, levels, index, method_name, [targets, unread])
|
|
198
|
+
next unsure = true if answer == :continue_unsure
|
|
199
|
+
next if answer == :continue
|
|
200
|
+
|
|
201
|
+
return unsure && answer.is_a?(Winner) ? UNKNOWN : answer
|
|
202
|
+
end
|
|
203
|
+
nil
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def level_answer(scope, levels, index, method_name, (targets, unread))
|
|
207
|
+
level_class, entries, mixed, every = levels[index]
|
|
208
|
+
return UNKNOWN if level_class && unread.key?(level_class)
|
|
209
|
+
|
|
210
|
+
refining = level_class && targets[level_class]
|
|
211
|
+
return Winner.new(module_name: refining, refined_class: level_class) if refining
|
|
212
|
+
return targeted_after?(levels, index, targets, unread) ? UNKNOWN : nil if mixed
|
|
213
|
+
|
|
214
|
+
walk_level(scope, [entries, every], level_class, method_name, [targets, unread])
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def walk_level(scope, (entries, every), level_class, method_name, (targets, unread))
|
|
218
|
+
if level_class.is_a?(SingletonClass)
|
|
219
|
+
return ClassObject.walk_level(scope, every || entries, method_name, targets, unread)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
walk_entries(scope, entries, level_class, method_name, targets, unread)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# A level's prepended modules, its own method and its included modules, in that order: `:continue` when none
|
|
226
|
+
# answers, so the walk moves on.
|
|
227
|
+
def walk_entries(scope, entries, level_class, method_name, targets, unread)
|
|
228
|
+
entries.each do |entry|
|
|
229
|
+
unless entry == level_class
|
|
230
|
+
return UNKNOWN if unread.key?(entry)
|
|
231
|
+
|
|
232
|
+
refining = targets[entry]
|
|
233
|
+
return Winner.new(module_name: refining, refined_class: entry) if refining
|
|
234
|
+
end
|
|
235
|
+
return nil if defines?(scope, entry, method_name)
|
|
236
|
+
end
|
|
237
|
+
:continue
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def targeted_after?(levels, index, targets, unread)
|
|
241
|
+
levels.drop(index).any? do |_level_class, entries|
|
|
242
|
+
entries.any? { |entry| targets.key?(entry) || unread.key?(entry) }
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# `[[level class, [entries in lookup order], mixed, every entry], …]` for an instance of `class_name`, or nil
|
|
247
|
+
# when the walk cannot be trusted: a chain cut at its limit, or one that records a mixin the tables cannot
|
|
248
|
+
# name. `every entry` (project levels only) is the level's entries in lookup order with each one that
|
|
249
|
+
# neither the project's resolver nor RBS can name kept as its candidate names (an Array) where `entries`
|
|
250
|
+
# leaves it out: the typer never reads it, and {OwnMethod} declines where a refinement may name one.
|
|
251
|
+
# `kind: :singleton` answers the class object `class_name` instead ({ClassObject.levels}, issue #1752).
|
|
252
|
+
def levels(scope, class_name, kind: :instance)
|
|
253
|
+
return ClassObject.levels(scope, class_name) if kind == :singleton
|
|
254
|
+
|
|
255
|
+
environment = scope.environment
|
|
256
|
+
return rbs_levels(scope, class_name) if environment&.class_known?(class_name)
|
|
257
|
+
|
|
258
|
+
project_levels(scope, class_name)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# The RBS ancestors, each a level of its own, `mixed` where the project reopens one to mix a module in (`class
|
|
262
|
+
# String; include Loud; end`), whose position among them RBS does not record. Each ancestor's name is a
|
|
263
|
+
# dependency, so a file that adds such a reopening re-checks the consumer.
|
|
264
|
+
def rbs_levels(scope, class_name)
|
|
265
|
+
loader = scope.environment.rbs_loader
|
|
266
|
+
names = loader ? loader.ancestor_names_for(class_name) : []
|
|
267
|
+
names = [class_name] if names.empty?
|
|
268
|
+
names.map { |name| [name, [name], project_mixin?(scope, name)] }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def project_mixin?(scope, name)
|
|
272
|
+
Analysis::DependencyRecorder.read_last_segment(:class, name) if Analysis::DependencyRecorder.active?
|
|
273
|
+
!scope.includes_of(name).empty? || scope.discovery.discovered_prepends.key?(name) ||
|
|
274
|
+
(scope.discovered_classes.key?(name) &&
|
|
275
|
+
Scope::ResolutionChain.for(scope, name, :instance, :methods).wildcard_mixin?)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def project_levels(scope, class_name)
|
|
279
|
+
chain = Scope::ResolutionChain.for(scope, class_name, :instance, :methods)
|
|
280
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
281
|
+
return nil if chain.truncated? || chain.wildcard_mixin?
|
|
282
|
+
|
|
283
|
+
out = []
|
|
284
|
+
chain.level_count.times do |index|
|
|
285
|
+
level_class = chain.level_classes[index]
|
|
286
|
+
every = chain.level_entries(index).map { |entry| entry_name(scope, entry) || unnamed(entry) }
|
|
287
|
+
names = every.grep(String)
|
|
288
|
+
next out << [level_class, names, false, every] if level_class
|
|
289
|
+
|
|
290
|
+
names.each { |name| out.concat(rbs_levels(scope, name)) }
|
|
291
|
+
end
|
|
292
|
+
out
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def unnamed(entry) = entry.candidates.empty? ? [entry.raw.to_s] : entry.candidates
|
|
296
|
+
|
|
297
|
+
def entry_name(scope, entry)
|
|
298
|
+
return entry.name unless entry.external?
|
|
299
|
+
|
|
300
|
+
environment = scope.environment
|
|
301
|
+
entry.candidates.find { |candidate| environment&.class_known?(candidate) }
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Does `name` define `method_name` on its instance side itself: a project `def` (its dependency edge recorded
|
|
305
|
+
# through `Scope#user_def_for`), another project definer (`attr_*`, `define_method`), or an RBS declaration
|
|
306
|
+
# written on `name` rather than on an ancestor? A {SingletonClass} asks its own singleton methods.
|
|
307
|
+
def defines?(scope, name, method_name)
|
|
308
|
+
return ClassObject.defines?(scope, name.of, method_name) if name.is_a?(SingletonClass)
|
|
309
|
+
return true if scope.user_def_for(name, method_name)
|
|
310
|
+
return true if scope.discovered_method?(name, method_name, :instance)
|
|
311
|
+
|
|
312
|
+
definition = ExternalAncestorResolution.method_definition(name, method_name, :instance, scope: scope)
|
|
313
|
+
ExternalAncestorResolution.declared_on_class?(definition, name)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
require_relative "refined_dispatch/class_object"
|
|
320
|
+
require_relative "refined_dispatch/own_method"
|
|
@@ -20,6 +20,8 @@ module Rigor
|
|
|
20
20
|
# `cache = {}; [1, 2].find { |e| (cache[:first] ||= e) == 2 }` answered its own `2`, where Ruby keeps the
|
|
21
21
|
# first iteration's `1`, folded `find` to `2` and reported `found == 2` always-truthy. The pass marks the
|
|
22
22
|
# sites this module returns (`Scope#with_repeated_or_writes`), and the reading is withheld at a marked site.
|
|
23
|
+
# `StatementEvaluator` types a repeating body from one entry scope too — its statement pass and every ADR-56
|
|
24
|
+
# write-back pass — and marks the generic pass's sites there (issue #1336).
|
|
23
25
|
#
|
|
24
26
|
# The mark is per site, not per receiver, because nothing about the receiver's type can say that its slot
|
|
25
27
|
# was filled: `{}` widens to `Hash[Dynamic[top], Dynamic[top]]`, a bare `Hash.new` is not widened at all, and
|
|
@@ -47,6 +47,19 @@ module Rigor
|
|
|
47
47
|
@unpositioned = nil
|
|
48
48
|
@hook_params = nil
|
|
49
49
|
@body_blocks = nil
|
|
50
|
+
@program = nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Marks the file's own top-level statements: a receiverless `include` among them is `main.include`,
|
|
54
|
+
# which mixes into `Object` (issue #1697).
|
|
55
|
+
def program_body(body)
|
|
56
|
+
return unless body.is_a?(Prism::StatementsNode)
|
|
57
|
+
|
|
58
|
+
@program = Set.new.compare_by_identity.merge(body.body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def program_statement?(node)
|
|
62
|
+
!@program.nil? && @program.include?(node)
|
|
50
63
|
end
|
|
51
64
|
|
|
52
65
|
# Marks the statements of a body whose own position is a fact — the calls and declarations in them
|