rigortype 0.4.1 → 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 +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 +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/03-configuration.md +1 -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/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 +21 -2
- data/lib/rigor/analysis/check_rules.rb +289 -57
- 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/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 +3 -1
- data/lib/rigor/analysis/incremental_session.rb +14 -1
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
- data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +34 -3
- data/lib/rigor/analysis/worker_session.rb +7 -4
- data/lib/rigor/cache/incremental_snapshot.rb +22 -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 +2 -1
- 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 +13 -7
- data/lib/rigor/cli.rb +10 -2
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +46 -2
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +283 -14
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment.rb +21 -3
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +215 -16
- 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 +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 +59 -12
- 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 +259 -28
- 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 +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_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/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +687 -60
- data/lib/rigor/inference/statement_evaluator.rb +71 -20
- 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/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 +29 -6
- data/lib/rigor/protection/mutation_cache.rb +1 -1
- 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 +189 -10
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +55 -5
- 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-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 +27 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- metadata +66 -1
|
@@ -42,8 +42,20 @@ module Rigor
|
|
|
42
42
|
# `Nominal[Grape::Endpoint]` (the block is `instance_eval`'d on an instance of that class, e.g. a
|
|
43
43
|
# route body on `Grape::Endpoint`), and `"singleton(Grape::API::Instance)"` binds
|
|
44
44
|
# `Singleton[Grape::API::Instance]` (the block is `instance_eval`'d on that *class object*, e.g.
|
|
45
|
-
# a `namespace` body on `Grape::API::Instance`).
|
|
46
|
-
#
|
|
45
|
+
# a `namespace` body on `Grape::API::Instance`). `:lexical` (issue #1667) leaves `self` as the caller's:
|
|
46
|
+
# the method runs the block where it was written (`block.refined(M).call`), so the entry only declares
|
|
47
|
+
# `refinements:`. Reserved Symbol names (`:receiver_singleton`, `:dsl_recorder`) remain unaccepted.
|
|
48
|
+
# - `refinements` — Array of fully-qualified module-name Strings (default `[]`, issue #1667, ADR-121 WD5).
|
|
49
|
+
# The method runs the block under `Proc#refined` with these modules (`instance_exec(&block.refined(M))`),
|
|
50
|
+
# so they are appended to the block body's in-effect refinements after its lexical list, in the declared
|
|
51
|
+
# order (the last one wins). Nested blocks inherit them. A module that refines nothing the project or a
|
|
52
|
+
# gem's source inference makes visible contributes nothing.
|
|
53
|
+
#
|
|
54
|
+
# ## Matching
|
|
55
|
+
#
|
|
56
|
+
# A `:receiver_instance` or `singleton(...)` entry matches `Singleton[X]` receivers only (a class-level DSL
|
|
57
|
+
# call). A named instance binding (`"Foo::Bar"`) and a `:lexical` entry also match `Nominal[X]` receivers:
|
|
58
|
+
# a `:lexical` method is as often an instance method (`ActiveRecord::Relation#where`) as a class-level one.
|
|
47
59
|
#
|
|
48
60
|
# ## Ractor-shareability
|
|
49
61
|
#
|
|
@@ -51,7 +63,8 @@ module Rigor
|
|
|
51
63
|
# mutable array does not leak into the value. `Ractor.shareable?` returns true after `#initialize`.
|
|
52
64
|
class BlockAsMethod
|
|
53
65
|
SELF_TYPE_RECEIVER_INSTANCE = :receiver_instance
|
|
54
|
-
|
|
66
|
+
SELF_TYPE_LEXICAL = :lexical
|
|
67
|
+
VALID_SELF_TYPES = [SELF_TYPE_RECEIVER_INSTANCE, SELF_TYPE_LEXICAL].freeze
|
|
55
68
|
|
|
56
69
|
CLASS_NAME_PATTERN = /[A-Z]\w*(?:::[A-Z]\w*)*/
|
|
57
70
|
# `self_type: "Foo::Bar"` — the block is `instance_eval`'d on an instance of `Foo::Bar`.
|
|
@@ -60,16 +73,18 @@ module Rigor
|
|
|
60
73
|
# object itself (Grape's `namespace`/`route_param` bodies run on `Grape::API::Instance`).
|
|
61
74
|
SINGLETON_SELF_TYPE_PATTERN = /\Asingleton\((#{CLASS_NAME_PATTERN})\)\z/
|
|
62
75
|
|
|
63
|
-
attr_reader :receiver_constraint, :method_names, :self_type
|
|
76
|
+
attr_reader :receiver_constraint, :method_names, :self_type, :refinements
|
|
64
77
|
|
|
65
|
-
def initialize(receiver_constraint:, method_names:, self_type: SELF_TYPE_RECEIVER_INSTANCE)
|
|
78
|
+
def initialize(receiver_constraint:, method_names:, self_type: SELF_TYPE_RECEIVER_INSTANCE, refinements: [])
|
|
66
79
|
validate_receiver_constraint!(receiver_constraint)
|
|
67
80
|
validate_method_names!(method_names)
|
|
68
81
|
validate_self_type!(self_type)
|
|
82
|
+
validate_refinements!(refinements)
|
|
69
83
|
|
|
70
84
|
@receiver_constraint = receiver_constraint.dup.freeze
|
|
71
85
|
@method_names = method_names.map(&:to_sym).freeze
|
|
72
86
|
@self_type = self_type.is_a?(String) ? self_type.dup.freeze : self_type
|
|
87
|
+
@refinements = refinements.map { |name| name.dup.freeze }.freeze
|
|
73
88
|
freeze
|
|
74
89
|
end
|
|
75
90
|
|
|
@@ -77,7 +92,8 @@ module Rigor
|
|
|
77
92
|
{
|
|
78
93
|
"receiver_constraint" => receiver_constraint,
|
|
79
94
|
"method_names" => method_names.map(&:to_s),
|
|
80
|
-
"self_type" => self_type.to_s
|
|
95
|
+
"self_type" => self_type.to_s,
|
|
96
|
+
"refinements" => refinements
|
|
81
97
|
}
|
|
82
98
|
end
|
|
83
99
|
|
|
@@ -85,12 +101,18 @@ module Rigor
|
|
|
85
101
|
other.is_a?(BlockAsMethod) &&
|
|
86
102
|
receiver_constraint == other.receiver_constraint &&
|
|
87
103
|
method_names == other.method_names &&
|
|
88
|
-
self_type == other.self_type
|
|
104
|
+
self_type == other.self_type &&
|
|
105
|
+
refinements == other.refinements
|
|
89
106
|
end
|
|
90
107
|
alias eql? ==
|
|
91
108
|
|
|
92
109
|
def hash
|
|
93
|
-
[receiver_constraint, method_names, self_type].hash
|
|
110
|
+
[receiver_constraint, method_names, self_type, refinements].hash
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Whether the block keeps the caller's `self` (`self_type: :lexical`).
|
|
114
|
+
def lexical_self?
|
|
115
|
+
self_type == SELF_TYPE_LEXICAL
|
|
94
116
|
end
|
|
95
117
|
|
|
96
118
|
# The class name a String `self_type` binds `self` to — `"Foo::Bar"` for both the plain and the
|
|
@@ -118,6 +140,12 @@ module Rigor
|
|
|
118
140
|
self_type.is_a?(String) && !singleton_binding?
|
|
119
141
|
end
|
|
120
142
|
|
|
143
|
+
# Whether `Nominal[X]` receivers match, beside `Singleton[X]` ones: a named instance binding, or a
|
|
144
|
+
# `:lexical` entry.
|
|
145
|
+
def matches_instance_receivers?
|
|
146
|
+
named_instance_binding? || lexical_self?
|
|
147
|
+
end
|
|
148
|
+
|
|
121
149
|
private
|
|
122
150
|
|
|
123
151
|
def validate_receiver_constraint!(value)
|
|
@@ -143,6 +171,21 @@ module Rigor
|
|
|
143
171
|
end
|
|
144
172
|
end
|
|
145
173
|
|
|
174
|
+
def validate_refinements!(refinements)
|
|
175
|
+
unless refinements.is_a?(Array)
|
|
176
|
+
raise ArgumentError,
|
|
177
|
+
"Plugin::Macro::BlockAsMethod#refinements must be an Array, got #{refinements.inspect}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
refinements.each do |name|
|
|
181
|
+
next if name.is_a?(String) && name.match?(NAMED_SELF_TYPE_PATTERN)
|
|
182
|
+
|
|
183
|
+
raise ArgumentError,
|
|
184
|
+
"Plugin::Macro::BlockAsMethod#refinements entries must be module-name Strings ('Foo::Bar'), " \
|
|
185
|
+
"got #{name.inspect}"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
146
189
|
def validate_self_type!(self_type)
|
|
147
190
|
return if VALID_SELF_TYPES.include?(self_type)
|
|
148
191
|
return if self_type.is_a?(String) &&
|
|
@@ -53,16 +53,37 @@ module Rigor
|
|
|
53
53
|
# ADR-93 auto-wired `rigor-rbs-inline` entry when the library is resolvable).
|
|
54
54
|
# @param source_files — the file(s) the command inspects. Threaded so each loaded plugin's
|
|
55
55
|
# `source_rbs_synthesizer` runs over them at env-build time; an empty list contributes no synthesized RBS.
|
|
56
|
-
|
|
56
|
+
# @param feature_sources — path => source text the required-feature scan reads in place of that file (an
|
|
57
|
+
# editor buffer bound with `--tmp-file` / `--instead-of`).
|
|
58
|
+
def build(configuration:, source_files:, feature_sources: {})
|
|
59
|
+
features = required_features(configuration, source_files, sources: feature_sources)
|
|
57
60
|
Environment.for_project(
|
|
58
61
|
libraries: configuration.libraries,
|
|
59
62
|
signature_paths: configuration.signature_paths,
|
|
60
63
|
plugin_registry: load_plugin_registry(configuration),
|
|
61
64
|
source_files: source_files,
|
|
65
|
+
required_features: features,
|
|
62
66
|
**dependency_discovery_options(configuration)
|
|
63
67
|
)
|
|
64
68
|
rescue StandardError
|
|
65
|
-
bare(configuration)
|
|
69
|
+
bare(configuration, features)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Issue #1700 — the {Environment::RequiredFeatures} the environment gates on: the configured paths plus
|
|
73
|
+
# `extra_files` (the file a probe inspects, which may lie outside them), the same set `rigor check` scans
|
|
74
|
+
# whichever files it is given, so a position types as `rigor check` types it.
|
|
75
|
+
def required_features(configuration, extra_files = [], sources: {})
|
|
76
|
+
Environment::RequiredFeatures.for_configuration(configuration, extra_files, sources: sources)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The required-feature scan's view of an editor buffer (`--tmp-file` / `--instead-of`): its logical path
|
|
80
|
+
# read from its physical file. Empty without a buffer or when the file cannot be read.
|
|
81
|
+
def buffer_sources(buffer)
|
|
82
|
+
return {} if buffer.nil?
|
|
83
|
+
|
|
84
|
+
{ buffer.logical_path => File.binread(buffer.physical_path) }
|
|
85
|
+
rescue SystemCallError
|
|
86
|
+
{}
|
|
66
87
|
end
|
|
67
88
|
|
|
68
89
|
# The dependency-discovery axes `check` reads off the configuration, as a keyword hash. Extracted so the
|
|
@@ -116,22 +137,24 @@ module Rigor
|
|
|
116
137
|
|
|
117
138
|
# The first fail-soft floor: no plugin tier and no synthesized RBS, but still the project's own dependency
|
|
118
139
|
# sources — dropping those is what issue #821 was, so a plugin-loading failure must not cost them.
|
|
119
|
-
def bare(configuration)
|
|
140
|
+
def bare(configuration, features = required_features(configuration))
|
|
120
141
|
Environment.for_project(
|
|
121
142
|
libraries: configuration.libraries,
|
|
122
143
|
signature_paths: configuration.signature_paths,
|
|
144
|
+
required_features: features,
|
|
123
145
|
**dependency_discovery_options(configuration)
|
|
124
146
|
)
|
|
125
147
|
rescue StandardError
|
|
126
|
-
minimal(configuration)
|
|
148
|
+
minimal(configuration, features)
|
|
127
149
|
end
|
|
128
150
|
|
|
129
151
|
# The last floor: RBS core plus the project's own signature paths, which cannot fail on anything outside
|
|
130
152
|
# the project itself.
|
|
131
|
-
def minimal(configuration)
|
|
153
|
+
def minimal(configuration, features = [])
|
|
132
154
|
Environment.for_project(
|
|
133
155
|
libraries: configuration.libraries,
|
|
134
|
-
signature_paths: configuration.signature_paths
|
|
156
|
+
signature_paths: configuration.signature_paths,
|
|
157
|
+
required_features: features
|
|
135
158
|
)
|
|
136
159
|
end
|
|
137
160
|
end
|
|
@@ -220,7 +220,7 @@ module Rigor
|
|
|
220
220
|
def patched_rows(patched)
|
|
221
221
|
patched.by_key.map do |(class_name, method_name, kind), entry|
|
|
222
222
|
[class_name.to_s, method_name.to_s, kind.to_s, entry.source_path.to_s, entry.source_line,
|
|
223
|
-
entry.return_type&.describe(:short).to_s]
|
|
223
|
+
entry.return_type&.describe(:short).to_s, entry.alias_of.to_s]
|
|
224
224
|
end.sort
|
|
225
225
|
end
|
|
226
226
|
|
|
@@ -27,11 +27,13 @@ module Rigor
|
|
|
27
27
|
# ancestors are `[C, A, M, Base]`. It reads the chain's `:constants` flavor, whose predicate for a
|
|
28
28
|
# project class also admits a module that holds only constants.
|
|
29
29
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
30
|
+
# Project ancestors appear, and (issue #1698) a module the class body `include`s or `prepend`s that
|
|
31
|
+
# only RBS or the class registry declares: `include AcLibraryRb` puts `AcLibraryRb` at its Ruby
|
|
32
|
+
# position, so `Segtree` inside the class reads `AcLibraryRb::Segtree` as Ruby does. Such an entry
|
|
33
|
+
# contributes its OWN constants only; the RBS ancestry behind it is not expanded, as for every external
|
|
34
|
+
# entry. An external SUPERCLASS (`class Foo < ActiveRecord::Base`) still contributes nothing, and a
|
|
35
|
+
# constant it owns resolves only if the bare name reaches it at step 3: widening to the superclass's
|
|
36
|
+
# RBS graph is a separate question with its own FP surface. See {.external_constant_owner}.
|
|
35
37
|
#
|
|
36
38
|
# Memoised per run because step 2 runs on every constant reference whose lexical candidates all
|
|
37
39
|
# miss — which is the common case for a core-class reference (`String` inside `class Foo`). The
|
|
@@ -52,7 +54,7 @@ module Rigor
|
|
|
52
54
|
scopes, chain = ancestor_constant_worlds(class_name, scope)
|
|
53
55
|
owner, hit = first_ancestor_hit(scopes, &)
|
|
54
56
|
verdict = chain.settle(scope, owner, owner: owner) do |retro|
|
|
55
|
-
first_ancestor_hit(constant_scopes_of(retro, class_name), &)&.first
|
|
57
|
+
first_ancestor_hit(constant_scopes_of(retro, class_name, scope), &)&.first
|
|
56
58
|
end
|
|
57
59
|
return hit if verdict == :chain
|
|
58
60
|
|
|
@@ -109,7 +111,9 @@ module Rigor
|
|
|
109
111
|
worlds = bucket[class_name]
|
|
110
112
|
return bucket[class_name] = compute_ancestor_constant_scopes(class_name, scope) if worlds.nil?
|
|
111
113
|
|
|
112
|
-
Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :constants)
|
|
114
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :constants)
|
|
115
|
+
chain.record(scope)
|
|
116
|
+
record_external_owner_edges(chain)
|
|
113
117
|
worlds
|
|
114
118
|
end
|
|
115
119
|
private_class_method :recorded_ancestor_constant_scopes
|
|
@@ -118,22 +122,134 @@ module Rigor
|
|
|
118
122
|
def compute_ancestor_constant_scopes(class_name, scope)
|
|
119
123
|
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :constants)
|
|
120
124
|
chain.record(scope)
|
|
121
|
-
|
|
125
|
+
record_external_owner_edges(chain)
|
|
126
|
+
scopes = constant_scopes_of(chain, class_name, scope)
|
|
122
127
|
[scopes, chain].freeze
|
|
123
128
|
end
|
|
124
129
|
private_class_method :compute_ancestor_constant_scopes
|
|
125
130
|
|
|
126
|
-
# The chain's project entries in order, each once, without `class_name` itself
|
|
127
|
-
|
|
131
|
+
# The chain's project entries in order, each once, without `class_name` itself, with each mixin entry
|
|
132
|
+
# the project does not declare standing as the module {.external_constant_owner} names.
|
|
133
|
+
def constant_scopes_of(chain, class_name, scope)
|
|
128
134
|
out = []
|
|
129
135
|
chain.entries.each do |entry|
|
|
130
|
-
name = entry.name
|
|
136
|
+
name = entry.external? ? external_constant_owner(entry, scope) : entry.name
|
|
131
137
|
out << name unless name.nil? || name == class_name || out.include?(name)
|
|
132
138
|
end
|
|
133
139
|
out.freeze
|
|
134
140
|
end
|
|
135
141
|
private_class_method :constant_scopes_of
|
|
136
142
|
|
|
143
|
+
# Issue #1698 — the module an external `include` / `prepend` entry names, when the constant ladder may
|
|
144
|
+
# read its constants, or nil. Ruby resolved the spelling at the `include` lexically, so the first of the
|
|
145
|
+
# entry's candidates (most qualified first) that exists is the module. The answer is the first candidate
|
|
146
|
+
# RBS or the class registry knows, and nil when a nearer candidate is a constant the project binds
|
|
147
|
+
# ({.project_written_candidate?}): Ruby's module is then that constant, whose constants nothing here
|
|
148
|
+
# describes. The candidates are the entry's own, behind the namespace of the body that wrote the edge
|
|
149
|
+
# (`entry.owner`), which Ruby searches first and the chain's resolver does not consult. An ambiguous
|
|
150
|
+
# spelling has no candidates and answers nil. A superclass edge answers nil (see
|
|
151
|
+
# {.ancestor_constant_scopes}).
|
|
152
|
+
def external_constant_owner(entry, scope)
|
|
153
|
+
return nil if entry.superclass_edge || entry.candidates.empty?
|
|
154
|
+
|
|
155
|
+
env = scope.environment
|
|
156
|
+
external_owner_candidates(entry).each do |candidate|
|
|
157
|
+
return nil if project_written_candidate?(candidate, scope)
|
|
158
|
+
return candidate if env.class_known?(candidate)
|
|
159
|
+
end
|
|
160
|
+
nil
|
|
161
|
+
end
|
|
162
|
+
private_class_method :external_constant_owner
|
|
163
|
+
|
|
164
|
+
def external_owner_candidates(entry)
|
|
165
|
+
return entry.candidates if entry.owner.nil? || entry.raw.start_with?("::")
|
|
166
|
+
|
|
167
|
+
["#{entry.owner}::#{entry.raw}", *entry.candidates]
|
|
168
|
+
end
|
|
169
|
+
private_class_method :external_owner_candidates
|
|
170
|
+
|
|
171
|
+
# ADR-46 — the name edges of {.external_constant_owner}'s guard, for every mixin entry it read: a file that
|
|
172
|
+
# starts writing or declaring a nearer spelling of the module's name (`User::AcLibraryRb = …` in a file the
|
|
173
|
+
# reader has no other edge to) moves the answer. Keyed on the spelling's last segment, as the ladder's own
|
|
174
|
+
# `constant:` and `class:` edges are. A memo hit replays them beside the chain's own edges.
|
|
175
|
+
def record_external_owner_edges(chain)
|
|
176
|
+
return unless Analysis::DependencyRecorder.active?
|
|
177
|
+
|
|
178
|
+
chain.entries.each do |entry|
|
|
179
|
+
next unless entry.external? && !entry.superclass_edge
|
|
180
|
+
|
|
181
|
+
Analysis::DependencyRecorder.read_last_segment(:constant, entry.raw)
|
|
182
|
+
Analysis::DependencyRecorder.read_last_segment(:class, entry.raw)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
private_class_method :record_external_owner_edges
|
|
186
|
+
|
|
187
|
+
# Issue #1698 — whether the walk must stop at `owner` because a module its RBS ancestry holds may own the name
|
|
188
|
+
# (`candidate` answers for it). Ruby searches that ancestry right after `owner`, before any later entry of
|
|
189
|
+
# the chain and before the top level, and the chain does not expand it. Where no module of it owns the
|
|
190
|
+
# name, where they sit does not matter to this name and the walk goes on.
|
|
191
|
+
#
|
|
192
|
+
# `owner` is an RBS-only mixin (`external`), or a project entry RBS also declares: a project file that
|
|
193
|
+
# reopens an RBS module without restating its `include`s makes it a project entry whose chain lists only
|
|
194
|
+
# the edges the project writes, so its ancestry is read less what that chain lists. The ancestry comes from
|
|
195
|
+
# the declarations (`RbsLoader#declared_ancestry`), so a definition that fails to build still yields what
|
|
196
|
+
# it declares. Where it cannot be read whole, {.ancestry_unknown?} keeps the later RBS-only mixins out
|
|
197
|
+
# instead of stopping here.
|
|
198
|
+
def mixin_ancestry_stop?(owner, scope, external)
|
|
199
|
+
loader = rbs_loader_for(scope, nil)
|
|
200
|
+
return false if loader.nil? || (!external && !loader.class_known?(owner))
|
|
201
|
+
|
|
202
|
+
names, = loader.declared_ancestry(owner)
|
|
203
|
+
listed = nil
|
|
204
|
+
names.any? do |ancestor|
|
|
205
|
+
next false if IMPLICIT_ANCESTORS.include?(ancestor)
|
|
206
|
+
|
|
207
|
+
listed ||= external ? EMPTY_NAMES : project_chain_names(owner, scope)
|
|
208
|
+
!listed.include?(ancestor) && yield(ancestor)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
private_class_method :mixin_ancestry_stop?
|
|
212
|
+
|
|
213
|
+
# Issue #1698 — whether an entry ahead of `entry` on `class_name`'s list has an RBS ancestry that cannot be
|
|
214
|
+
# read whole ({.mixin_ancestry_stop?}): a module it hides may own the name, so `entry`, an RBS-only mixin,
|
|
215
|
+
# MUST NOT answer. Only those entries are kept out; project entries, the superclass chain and the top level
|
|
216
|
+
# answer as they did before #1698.
|
|
217
|
+
def ancestry_unknown_before?(class_name, entry, scope)
|
|
218
|
+
loader = rbs_loader_for(scope, nil)
|
|
219
|
+
return true if loader.nil?
|
|
220
|
+
|
|
221
|
+
ancestor_constant_scopes(class_name, scope).each do |earlier|
|
|
222
|
+
break if earlier == entry
|
|
223
|
+
next unless loader.class_known?(earlier)
|
|
224
|
+
|
|
225
|
+
return true unless loader.declared_ancestry(earlier)[1]
|
|
226
|
+
end
|
|
227
|
+
false
|
|
228
|
+
end
|
|
229
|
+
private_class_method :ancestry_unknown_before?
|
|
230
|
+
|
|
231
|
+
# Ruby places these after every entry of a chain, so a constant they own is the top level's (step 4).
|
|
232
|
+
IMPLICIT_ANCESTORS = %w[Object Kernel BasicObject].freeze
|
|
233
|
+
private_constant :IMPLICIT_ANCESTORS
|
|
234
|
+
|
|
235
|
+
EMPTY_NAMES = [].freeze
|
|
236
|
+
private_constant :EMPTY_NAMES
|
|
237
|
+
|
|
238
|
+
# Every name `owner`'s own instance chain lists: its project entries and each external entry's candidates.
|
|
239
|
+
def project_chain_names(owner, scope)
|
|
240
|
+
Scope::ResolutionChain.for(scope, owner, :instance, :constants).entries.flat_map do |entry|
|
|
241
|
+
entry.external? ? entry.candidates : [entry.name]
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
private_class_method :project_chain_names
|
|
245
|
+
|
|
246
|
+
# Whether the project binds `candidate` itself: a namespace under the `:constants` flavor's tables, or a
|
|
247
|
+
# constant write the census records (#1290), typed or not.
|
|
248
|
+
def project_written_candidate?(candidate, scope)
|
|
249
|
+
known_project_namespace?(candidate, scope) || scope.shadowing_constant_names(candidate).include?(candidate)
|
|
250
|
+
end
|
|
251
|
+
private_class_method :project_written_candidate?
|
|
252
|
+
|
|
137
253
|
# Whether `name` is a namespace the project declares — the predicate the chain's `:constants` flavor
|
|
138
254
|
# resolves ancestor names with, and the path walk's (`reflection/constant_path.rb`) test for a head.
|
|
139
255
|
def known_project_namespace?(name, scope)
|
|
@@ -118,11 +118,15 @@ module Rigor
|
|
|
118
118
|
|
|
119
119
|
# {.agreed_ancestor_hit} over the budget-bounded scope lists: the first answer where
|
|
120
120
|
# `Scope::ResolutionChain#settle` lets the chain stand, and otherwise the breadth-first order's.
|
|
121
|
+
#
|
|
122
|
+
# Issue #1698 — the path walk reads the project entries only: an RBS-only mixin's own ancestry is not on the
|
|
123
|
+
# chain, so the rung that reads such a mixin also stops behind it ({.mixin_ancestry_stop?}), and this
|
|
124
|
+
# walk keeps the answer it gave before.
|
|
121
125
|
def bounded_agreed_hit(class_name, scope, &)
|
|
122
126
|
scopes, chain = ancestor_constant_worlds(class_name, scope)
|
|
123
|
-
owner, hit = first_ancestor_hit(bounded(scopes), &)
|
|
127
|
+
owner, hit = first_ancestor_hit(bounded(project_scopes(scopes, scope)), &)
|
|
124
128
|
verdict = chain.settle(scope, owner, owner: owner) do |retro|
|
|
125
|
-
first_ancestor_hit(bounded(constant_scopes_of(retro, class_name)), &)&.first
|
|
129
|
+
first_ancestor_hit(bounded(project_scopes(constant_scopes_of(retro, class_name, scope), scope)), &)&.first
|
|
126
130
|
end
|
|
127
131
|
return hit if verdict == :chain
|
|
128
132
|
|
|
@@ -130,6 +134,13 @@ module Rigor
|
|
|
130
134
|
end
|
|
131
135
|
private_class_method :bounded_agreed_hit
|
|
132
136
|
|
|
137
|
+
def project_scopes(scopes, scope)
|
|
138
|
+
return scopes if scopes.all? { |name| known_project_namespace?(name, scope) }
|
|
139
|
+
|
|
140
|
+
scopes.select { |name| known_project_namespace?(name, scope) }
|
|
141
|
+
end
|
|
142
|
+
private_class_method :project_scopes
|
|
143
|
+
|
|
133
144
|
# How many of {.ancestor_constant_scopes}' entries one segment may consult, under
|
|
134
145
|
# `Scope::ANCESTOR_WALK_LIMIT` — the budget `Scope`'s method lookup already spends on this same
|
|
135
146
|
# graph, reused rather than re-chosen so one pathological ancestry costs one bound. The list
|
data/lib/rigor/reflection.rb
CHANGED
|
@@ -50,6 +50,11 @@ module Rigor
|
|
|
50
50
|
WRITTEN_CANDIDATE = Object.new.freeze
|
|
51
51
|
private_constant :WRITTEN_CANDIDATE
|
|
52
52
|
|
|
53
|
+
# Issue #1698 — what the constant ladder's ancestor rung answers where an entry's RBS ancestry may
|
|
54
|
+
# own the name ({.mixin_ancestry_stop?}). Never a type: {.lexical_constant_type} replaces it.
|
|
55
|
+
MIXIN_ANCESTRY_STOP = Object.new.freeze
|
|
56
|
+
private_constant :MIXIN_ANCESTRY_STOP
|
|
57
|
+
|
|
53
58
|
module_function
|
|
54
59
|
|
|
55
60
|
def class_known?(class_name, scope: Scope.empty)
|
|
@@ -197,10 +202,19 @@ module Rigor
|
|
|
197
202
|
# `P::A < P::Base` is `P::Base::B`, and step 3's `A::B` is a real but different class.
|
|
198
203
|
#
|
|
199
204
|
# Step 3 — the bare name (top level). It takes no `shadows`: nothing sits below it for a stop to replace.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
#
|
|
206
|
+
# Issue #1698 — step 2 answers {MIXIN_ANCESTRY_STOP} where an RBS-only mixin's own ancestry may own the name
|
|
207
|
+
# ({.mixin_ancestry_stop?}). Ruby reads that ancestry before any later ancestor or the top level, and
|
|
208
|
+
# the chain does not list it, so a lower rung's answer is replaced by `Dynamic[top]`, as #1290's stop is.
|
|
209
|
+
lexical = first_constant_hit(lexical_nesting_chain(scope), name, scope, shadows)
|
|
210
|
+
return lexical if lexical
|
|
211
|
+
|
|
212
|
+
ancestor = ancestor_constant_type(name, scope, enclosing_class_path(scope), shadows)
|
|
213
|
+
stopped = ancestor.equal?(MIXIN_ANCESTRY_STOP)
|
|
214
|
+
return ancestor if ancestor && !stopped
|
|
215
|
+
|
|
216
|
+
below = constant_path_type(name, scope, shadows: shadows) || constant_type_at(name, scope)
|
|
217
|
+
stopped && below ? Type::Combinator.untyped : below
|
|
204
218
|
end
|
|
205
219
|
private_class_method :lexical_constant_type
|
|
206
220
|
|
|
@@ -216,11 +230,28 @@ module Rigor
|
|
|
216
230
|
end
|
|
217
231
|
private_class_method :first_constant_hit
|
|
218
232
|
|
|
219
|
-
# Step 2's rung on its own: nil when there is no enclosing class path to take ancestors of
|
|
233
|
+
# Step 2's rung on its own: nil when there is no enclosing class path to take ancestors of, and
|
|
234
|
+
# {MIXIN_ANCESTRY_STOP} where an RBS-only mixin's ancestry may own the name.
|
|
235
|
+
#
|
|
236
|
+
# Issue #1698 — an entry that stands for an RBS-only mixin ({.external_constant_owner}) is skipped where `self`
|
|
237
|
+
# is a class object: a `class << self` body and a `def` in it run under the singleton class's cref, whose
|
|
238
|
+
# ancestors are not the class's ([#1305](https://github.com/rigortype/rigor/issues/1305)), and the scope does
|
|
239
|
+
# not tell such a body from a `def self.m` or the class body. Skipping keeps the answer this rung gave before
|
|
240
|
+
# #1698 there.
|
|
220
241
|
def ancestor_constant_type(name, scope, prefix, shadows)
|
|
221
242
|
return nil if prefix.nil? || prefix.empty?
|
|
222
243
|
|
|
223
|
-
|
|
244
|
+
class_object_self = scope.self_type.is_a?(Type::Singleton)
|
|
245
|
+
agreed_ancestor_hit(prefix, scope) do |entry|
|
|
246
|
+
external = !known_project_namespace?(entry, scope)
|
|
247
|
+
next nil if external && (class_object_self || ancestry_unknown_before?(prefix, entry, scope))
|
|
248
|
+
|
|
249
|
+
candidate = ->(owner) { constant_type_at("#{owner}::#{name}", scope, shadows) }
|
|
250
|
+
hit = candidate.call(entry)
|
|
251
|
+
next hit if hit
|
|
252
|
+
|
|
253
|
+
MIXIN_ANCESTRY_STOP if mixin_ancestry_stop?(entry, scope, external, &candidate)
|
|
254
|
+
end
|
|
224
255
|
end
|
|
225
256
|
private_class_method :ancestor_constant_type
|
|
226
257
|
|
|
@@ -264,8 +295,9 @@ module Rigor
|
|
|
264
295
|
prefix = enclosing_class_path(scope)
|
|
265
296
|
return nil if prefix.nil? || prefix.empty?
|
|
266
297
|
|
|
267
|
-
first_constant_hit(peeled_nesting(prefix), name, scope, shadows) ||
|
|
268
|
-
|
|
298
|
+
hit = first_constant_hit(peeled_nesting(prefix), name, scope, shadows) ||
|
|
299
|
+
ancestor_constant_type(name, scope, prefix, shadows)
|
|
300
|
+
hit.equal?(MIXIN_ANCESTRY_STOP) ? nil : hit
|
|
269
301
|
end
|
|
270
302
|
private_class_method :caller_derived_constant_type
|
|
271
303
|
|
|
@@ -51,6 +51,7 @@ module Rigor
|
|
|
51
51
|
:clears_last_status,
|
|
52
52
|
:defines_case_equality,
|
|
53
53
|
:implicit_self_evidence,
|
|
54
|
+
:in_effect_refinements,
|
|
54
55
|
:possible_discovered_methods,
|
|
55
56
|
:possible_discovered_deferred_ranges,
|
|
56
57
|
:contested_discovered_def_nodes,
|
|
@@ -155,6 +156,7 @@ module Rigor
|
|
|
155
156
|
patched_line_readers: "the gets / readline names the file defines through the define_method family",
|
|
156
157
|
clears_last_status: "whether the analysed file holds a call that may set $? to nil",
|
|
157
158
|
defines_case_equality: "whether the analysed file holds a define_method naming ===",
|
|
159
|
+
in_effect_refinements: "the file's ordered in-effect refinements; built lazily from its tree, never seeded",
|
|
158
160
|
implicit_self_evidence: "where the file's implicit-self readers sit; built lazily from its tree, never seeded"
|
|
159
161
|
}.freeze,
|
|
160
162
|
run_state: {
|
|
@@ -217,12 +219,36 @@ module Rigor
|
|
|
217
219
|
# - {ENVELOPE_REFINED_MARK}: a `refine` block refines the class, which redefines its methods wherever a `using`
|
|
218
220
|
# is in effect. Issue #1367 splits it from {ENVELOPE_DYNAMIC_MARK}, which it used to share: an implicit
|
|
219
221
|
# conversion (`to_str`, `to_int`) ignores a refinement, so the `global.*` rules must tell the two apart.
|
|
220
|
-
#
|
|
222
|
+
# The readers that ask whether a surface is rewritten read both ({.rewritten_surface?}), except
|
|
223
|
+
# `call.wrong-arity`'s source envelope (`CheckRules::SourceArity`), which leaves a refinement to the per-call
|
|
224
|
+
# decline that knows the name and the `using` (issue #1797).
|
|
225
|
+
# Issue #1820 — the kind of a `[ENVELOPE_REMOVED_KIND, method_name]` bucket entry: the project removes the name
|
|
226
|
+
# from the class with a literal `undef`, `remove_method` or `undef_method`, whichever side. A removal is not
|
|
227
|
+
# otherwise distinguishable from an `attr_*` or `define_method`, whose envelope is opaque too, and the
|
|
228
|
+
# refinement own-definer proof must not take a removed method as the one Ruby finds.
|
|
229
|
+
ENVELOPE_REMOVED_KIND = :removed
|
|
230
|
+
# Issue #1820 — a `K = Class.new(Parent)` constant write: the class's superclass is not in the superclass table
|
|
231
|
+
# (the meta-new walk does not route `Parent` through ancestor discovery), so a reader that must place `K` in a
|
|
232
|
+
# hierarchy reads its superclass as unknown, as it reads the `nil` the table records for `class K < <expr>`.
|
|
233
|
+
ENVELOPE_META_SUPERCLASS_MARK = :"<meta-superclass>"
|
|
221
234
|
ENVELOPE_MODULE_MARK = :"<module>"
|
|
222
235
|
ENVELOPE_DYNAMIC_MARK = :"<dynamic>"
|
|
223
236
|
ENVELOPE_OBJECT_EXTENDED_MARK = :"<object-extended>"
|
|
224
237
|
ENVELOPE_REFINED_MARK = :"<refined>"
|
|
225
238
|
|
|
239
|
+
# Issue #1820 — whether an envelope bucket says the project declares, or rewrites, the name's method table: a
|
|
240
|
+
# per-method entry, the {ENVELOPE_MODULE_MARK} a `module` declaration records, or the {ENVELOPE_DYNAMIC_MARK}
|
|
241
|
+
# (`Comparable.module_eval { define_method(computed) }` gives a module a method no table names, so a reader
|
|
242
|
+
# must keep it on its chain rather than read RBS's surface for it). A bucket that holds only the refined,
|
|
243
|
+
# object-extended or meta-superclass marks is evidence about a name the project may only mention, such as
|
|
244
|
+
# `Comparable` in `x.singleton_class.prepend(Comparable)`.
|
|
245
|
+
DECLARING_MARKS = [ENVELOPE_MODULE_MARK, ENVELOPE_DYNAMIC_MARK].freeze
|
|
246
|
+
private_constant :DECLARING_MARKS
|
|
247
|
+
|
|
248
|
+
def self.declares_surface?(bucket)
|
|
249
|
+
!bucket.nil? && bucket.any? { |key, _| !key.is_a?(Symbol) || DECLARING_MARKS.include?(key) }
|
|
250
|
+
end
|
|
251
|
+
|
|
226
252
|
# Whether an envelope bucket says the project rewrites the class's method table beyond its literal names: a
|
|
227
253
|
# dynamic rewrite, or a refinement.
|
|
228
254
|
def self.rewritten_surface?(bucket)
|
|
@@ -236,6 +262,14 @@ module Rigor
|
|
|
236
262
|
# present. Not a constant character, so no class can be named it.
|
|
237
263
|
ENVELOPE_PROJECT_WIDE = "<project-wide>"
|
|
238
264
|
|
|
265
|
+
# ADR-121 WD7 (issues #1796, #1799) — the wildcard of `discovered_refinements`, standing for what the walk could
|
|
266
|
+
# not read. As a refined-class key it is a class the walk cannot name (`refine(k)`, `refine(self)`); as a method
|
|
267
|
+
# key, names a refine body defines that the walk cannot enumerate (`import_methods`, a computed
|
|
268
|
+
# `define_method`); as a refining module, a module the walk cannot name (a `refine` in an instance method,
|
|
269
|
+
# which any module extending it may run). `{"*" => {"*" => [M]}}` makes `M` opaque: some `refine` of `M` is
|
|
270
|
+
# unreadable. No constant can be named `*`, and method keys are Symbols, so neither axis collides.
|
|
271
|
+
REFINEMENT_WILDCARD = "*"
|
|
272
|
+
|
|
239
273
|
# The shared all-empty index `Scope.empty` (and every scope that never sees a seeding pass) points at — one
|
|
240
274
|
# allocation per process.
|
|
241
275
|
EMPTY = new(
|
|
@@ -417,6 +451,11 @@ module Rigor
|
|
|
417
451
|
# them by. Filled by `Inference::ScopeIndexer.index` from the file's own tree only; nil, where no file was
|
|
418
452
|
# indexed, declines every such reader.
|
|
419
453
|
implicit_self_evidence: nil,
|
|
454
|
+
# Issue #1673 (ADR-121 WD1) — the file's in-effect refinements (`Inference::InEffectRefinements`, walked on the
|
|
455
|
+
# first ask), which `Scope#in_effect_refinements` reads for the typer and the check rules read off the root's
|
|
456
|
+
# scope. Filled by `Inference::ScopeIndexer.index` from the file's own tree only; nil, where no file was
|
|
457
|
+
# indexed, answers no lexical refinement.
|
|
458
|
+
in_effect_refinements: nil,
|
|
420
459
|
# ADR-119 WD1 — the siblings of the members {SIBLINGS} pairs, empty until a producer admits a possible fact.
|
|
421
460
|
possible_discovered_methods: EMPTY_TABLE,
|
|
422
461
|
possible_discovered_deferred_ranges: EMPTY_TABLE,
|