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
|
@@ -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,
|
|
@@ -83,10 +83,12 @@ module Rigor
|
|
|
83
83
|
# One ancestor on a chain. A project entry names a class or module (`name`) on `side` `:instance` (its
|
|
84
84
|
# instance methods) or `:singleton` (the class object's singleton). An external entry has no `name`: it
|
|
85
85
|
# keeps the spelling (`raw`) and its candidate names (`candidates`, most qualified first, empty for an
|
|
86
|
-
# ambiguous spelling), and `superclass_edge` says it was reached through a superclass edge.
|
|
86
|
+
# ambiguous spelling), and `superclass_edge` says it was reached through a superclass edge. `owner` names
|
|
87
|
+
# the class or module whose edge an external entry is (#1698: Ruby resolves an `include`'s argument with
|
|
88
|
+
# that body as the innermost cref, a namespace `candidates` leaves out); it is nil on a project entry.
|
|
87
89
|
# `last_segment` is the unqualified name a project entry files its negative class edge under, computed once
|
|
88
90
|
# when the entry is interned.
|
|
89
|
-
Entry = Data.define(:name, :side, :raw, :candidates, :superclass_edge, :last_segment) do
|
|
91
|
+
Entry = Data.define(:name, :side, :raw, :candidates, :superclass_edge, :last_segment, :owner) do
|
|
90
92
|
def external? = name.nil?
|
|
91
93
|
end
|
|
92
94
|
|
|
@@ -98,7 +100,9 @@ module Rigor
|
|
|
98
100
|
# module whose mixin edges' order is not a fact, `kind` the side read (`:include` or `:extend`), `listed`
|
|
99
101
|
# the entries `DiscoveryIndex#unpositioned_mixins` lists for it (`"*"` included), and `multi_file` whether
|
|
100
102
|
# the multi-file rule fired (two or more files declare it and it has two or more edges on that side).
|
|
101
|
-
# Interned per `[node, kind]` in the flavor's bucket, so equal marks are one object.
|
|
103
|
+
# Interned per `[node, kind]` in the flavor's bucket, so equal marks are one object. Kind `:extend_hook`
|
|
104
|
+
# (#1730, listing `"*"`) marks a node a module it extends may mix into through a hook; it is not an order mark
|
|
105
|
+
# ({#order_marks}).
|
|
102
106
|
Mark = Data.define(:node, :kind, :listed, :multi_file)
|
|
103
107
|
|
|
104
108
|
NO_MARKS = [].freeze
|
|
@@ -135,7 +139,20 @@ module Rigor
|
|
|
135
139
|
|
|
136
140
|
# True when a class on the chain has mixin edges whose order the tables cannot vouch for
|
|
137
141
|
# (`DiscoveryIndex#unpositioned_mixins`, or a class declared in several files with several edges).
|
|
138
|
-
def unsettled? =
|
|
142
|
+
def unsettled? = !order_marks.empty?
|
|
143
|
+
|
|
144
|
+
# The marks that make the chain's ORDER doubtful. An `:extend_hook` mark (#1730) says only that an extended
|
|
145
|
+
# module's hook may have mixed something in that the chain does not list: it makes a candidate-set read
|
|
146
|
+
# `:unknown` and {#wildcard_mixin?} true, and leaves every other reader on the chain's own order. Sending
|
|
147
|
+
# those readers to master's order would answer a constant lookup breadth-first, which is wrong for the
|
|
148
|
+
# #1567 shape on every class that extends such a module.
|
|
149
|
+
def order_marks
|
|
150
|
+
@memo[:order_marks] ||= @marks.reject { |mark| mark.kind == :extend_hook }.freeze
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# True when a class on the chain records a mixin the tables cannot name (`"*"`: an `include` of a
|
|
154
|
+
# non-constant, or a call the walk cannot record), so the chain may hold a module it does not list.
|
|
155
|
+
def wildcard_mixin? = @marks.any? { |mark| mark.listed.include?(Relevance::WILDCARD) }
|
|
139
156
|
|
|
140
157
|
# The ONE decision every first-definer reader makes: does `answer`, read off this chain, stand, or does
|
|
141
158
|
# the reader answer what the walk this chain replaced answered ({MasterOrder})? Returns `:chain` or
|
|
@@ -163,7 +180,7 @@ module Rigor
|
|
|
163
180
|
# goes; `search` files only the entries ahead of an answer. `owner` names the entry that answered.
|
|
164
181
|
record_beyond(scope, owner, unknown_for.nil?)
|
|
165
182
|
return settle_unknown(scope, answer, unknown_for, &) unless unknown_for.nil?
|
|
166
|
-
return :master unless
|
|
183
|
+
return :master unless order_marks.empty?
|
|
167
184
|
|
|
168
185
|
case @forks
|
|
169
186
|
when 0 then :chain
|
|
@@ -208,7 +225,10 @@ module Rigor
|
|
|
208
225
|
def search(scope, start = 0, stop = nil, side: nil)
|
|
209
226
|
recording = Analysis::DependencyRecorder.active?
|
|
210
227
|
stop ||= @entries.size
|
|
211
|
-
|
|
228
|
+
if recording
|
|
229
|
+
record_head(scope, start, side)
|
|
230
|
+
record_extend_hooks(scope)
|
|
231
|
+
end
|
|
212
232
|
index = start
|
|
213
233
|
while index < stop
|
|
214
234
|
entry = @entries[index]
|
|
@@ -228,6 +248,7 @@ module Rigor
|
|
|
228
248
|
|
|
229
249
|
ResolutionChain.record_class(scope, @root)
|
|
230
250
|
(stop || @entries.size).times { |index| ResolutionChain.record_entry(scope, @entries[index], side) }
|
|
251
|
+
record_extend_hooks(scope)
|
|
231
252
|
end
|
|
232
253
|
|
|
233
254
|
# What a {#settle} verdict adds to what the search already filed. The search files the root and every entry
|
|
@@ -241,7 +262,8 @@ module Rigor
|
|
|
241
262
|
# it `:unknown`, so the rest of the chain is filed as for any other answer;
|
|
242
263
|
# - otherwise every entry after the answer (the whole chain when `owner` names none) files its class edge
|
|
243
264
|
# and, for a project entry, the negative class edge on its unqualified name, so a NEW file declaring or
|
|
244
|
-
# reopening it re-checks the reader. An external entry files only the sites of the names it can denote
|
|
265
|
+
# reopening it re-checks the reader. An external entry files only the sites of the names it can denote,
|
|
266
|
+
# and every external on the chain files its hook edges ({#record_hooks}, #1641).
|
|
245
267
|
#
|
|
246
268
|
# What is filed is a function of this chain, the entry the filing starts from and the discovery index the
|
|
247
269
|
# sites are read from, so a consumer that settles the same chain from the same entry again (the common case:
|
|
@@ -253,6 +275,8 @@ module Rigor
|
|
|
253
275
|
|
|
254
276
|
start = owner.nil? ? nil : @entries.index { |entry| entry.name == owner }
|
|
255
277
|
Analysis::DependencyRecorder.file_chain_once(self, start, scope.discovery) { file_beyond(scope, start) }
|
|
278
|
+
record_hooks(scope)
|
|
279
|
+
record_extend_hooks(scope)
|
|
256
280
|
end
|
|
257
281
|
private :record_beyond
|
|
258
282
|
|
|
@@ -268,6 +292,113 @@ module Rigor
|
|
|
268
292
|
end
|
|
269
293
|
private :file_beyond
|
|
270
294
|
|
|
295
|
+
# #1641 — the hook edges of every EXTERNAL entry on the chain, ahead of the answer or past it. A project file
|
|
296
|
+
# that gives an external module a hook (`module Comparable; def self.included(base) = base.prepend(P); end`)
|
|
297
|
+
# can put a definer ahead of the answer wherever the module sits, and a candidate-set read declines on it
|
|
298
|
+
# (the hook's mixin lists `"*"` on the module). The entry's class edges are the sites of the names it can
|
|
299
|
+
# denote, which a NEW file is not among, and a negative class edge on its name would re-check every reader of
|
|
300
|
+
# `Kernel` or `Enumerable` on any reopening. So each candidate name files the negative method edge of every
|
|
301
|
+
# {Relevance::HOOKS} name on its singleton side (`method:Comparable.included`, the key a new `def
|
|
302
|
+
# self.included` satisfies); a file adding any other method to the module re-checks nothing, and once the
|
|
303
|
+
# module is declared its sites carry later edits and the removal. The edges are a function of the chain alone,
|
|
304
|
+
# so a consumer files them once per chain ({Analysis::DependencyRecorder.file_chain_once}, #1590). A hook
|
|
305
|
+
# written another way — an instance `def included` in a module extended onto the external, a
|
|
306
|
+
# `define_singleton_method(:included)` — satisfies no such key and is not seen.
|
|
307
|
+
def record_hooks(scope)
|
|
308
|
+
externals = (@memo[:externals] ||= @entries.select(&:external?).freeze)
|
|
309
|
+
return if externals.empty?
|
|
310
|
+
|
|
311
|
+
Analysis::DependencyRecorder.file_chain_once(self, :hooks, scope.discovery) do
|
|
312
|
+
externals.each do |entry|
|
|
313
|
+
entry.candidates.each { |name| Analysis::DependencyRecorder.read_keys(ResolutionChain.hook_keys(name)) }
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
private :record_hooks
|
|
318
|
+
|
|
319
|
+
# #1730 — the edges of the {Builder#extend_hook_taint?} decision: an instance chain's project entries may carry
|
|
320
|
+
# a `"*"` mark because a module one of them `extend`s has a hook that mixes into the extender, and the
|
|
321
|
+
# module is often declared in a file the chain records nothing of. Each such module the decision read files
|
|
322
|
+
# its class edges (an edit to its hook moves its declaration signature) and the negative method edges of
|
|
323
|
+
# the hooks the decision reads ({ResolutionChain.extend_hook_keys}), which a NEW file reopening the module
|
|
324
|
+
# to add a hook satisfies; a spelling no project module answers files the singleton hook keys of each name
|
|
325
|
+
# it can denote, as {#record_hooks} does. Filed once per consumer and chain.
|
|
326
|
+
def record_extend_hooks(scope)
|
|
327
|
+
return unless @side == :instance
|
|
328
|
+
|
|
329
|
+
targets = (@memo[:extend_targets] ||= ResolutionChain.extend_targets(scope, @flavor, @entries))
|
|
330
|
+
return if targets.empty?
|
|
331
|
+
|
|
332
|
+
Analysis::DependencyRecorder.file_chain_once(self, :extend_hooks, scope.discovery) do
|
|
333
|
+
targets.each do |raw, resolved, candidates|
|
|
334
|
+
Analysis::DependencyRecorder.read_last_segment(:class, raw)
|
|
335
|
+
if resolved
|
|
336
|
+
ResolutionChain.record_class(scope, resolved)
|
|
337
|
+
Analysis::DependencyRecorder.read_keys(ResolutionChain.extend_hook_keys(resolved))
|
|
338
|
+
else
|
|
339
|
+
candidates.each { |name| Analysis::DependencyRecorder.read_keys(ResolutionChain.hook_keys(name)) }
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
private :record_extend_hooks
|
|
345
|
+
|
|
346
|
+
# The hooks that let a module `extend`ed onto N reshape N's INSTANCE side (#1730): its singleton `extended` /
|
|
347
|
+
# `extend_object` run against N at the `extend` and may mix into N; its instance `included` /
|
|
348
|
+
# `append_features` / `prepended` / `prepend_features` / `inherited` become N's own singleton hooks and run
|
|
349
|
+
# against N's includers, prependers and subclasses, which the mark on N reaches through their chains.
|
|
350
|
+
SINGLETON_EXTEND_HOOKS = %i[extended extend_object].freeze
|
|
351
|
+
INSTANCE_EXTEND_HOOKS = %i[included append_features prepended prepend_features inherited].freeze
|
|
352
|
+
|
|
353
|
+
@extend_hook_keys = {}
|
|
354
|
+
|
|
355
|
+
# The frozen negative method keys of `name`'s {SINGLETON_EXTEND_HOOKS} and {INSTANCE_EXTEND_HOOKS}.
|
|
356
|
+
def self.extend_hook_keys(name)
|
|
357
|
+
@extend_hook_keys[name] ||= (
|
|
358
|
+
SINGLETON_EXTEND_HOOKS.map { |hook| "method:#{name}.#{hook}".freeze } +
|
|
359
|
+
INSTANCE_EXTEND_HOOKS.map { |hook| "method:#{name}##{hook}".freeze }
|
|
360
|
+
).freeze
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Whether extending `name` may mix modules into the extender's instance side, or into its includers' (#1730):
|
|
364
|
+
# `name` records a mixin it cannot name on its own instance side (`"*"`, which a hook's `base.include X` or
|
|
365
|
+
# `base.class_eval { … }` lists on the hook's module) and defines one of the hooks that would run it there.
|
|
366
|
+
def self.extend_reshapes_instance?(scope, name)
|
|
367
|
+
listed = scope.discovery.unpositioned_mixins[name]&.dig(:include)
|
|
368
|
+
return false unless listed&.include?(Relevance::WILDCARD)
|
|
369
|
+
|
|
370
|
+
defines_any?(scope.discovered_singleton_def_nodes[name], SINGLETON_EXTEND_HOOKS) ||
|
|
371
|
+
defines_any?(scope.discovered_def_nodes[name], INSTANCE_EXTEND_HOOKS)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Read off the def-node tables: the existence table does not carry a module's own instance hook once the
|
|
375
|
+
# extend fold has copied it onto the extender's singleton.
|
|
376
|
+
def self.defines_any?(defs, hooks)
|
|
377
|
+
!defs.nil? && hooks.any? { |hook| defs.key?(hook) }
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# `[[spelling, resolved name or nil, candidate names], …]`: every module the project entries of an instance chain
|
|
381
|
+
# `extend`, as {Builder#extend_hook_taint?} resolved them.
|
|
382
|
+
def self.extend_targets(scope, flavor, entries)
|
|
383
|
+
resolver = resolver_for(scope, flavor)
|
|
384
|
+
extends = scope.discovery.discovered_extends
|
|
385
|
+
entries.each_with_object([]) do |entry, out|
|
|
386
|
+
next if entry.external? || entry.side != :instance
|
|
387
|
+
|
|
388
|
+
(extends[entry.name] || EMPTY_NAMES).each do |raw|
|
|
389
|
+
resolved = resolver.resolve_one(entry.name, raw)
|
|
390
|
+
out << [raw, resolved, resolved ? EMPTY_NAMES : resolver.candidates(entry.name, raw)].freeze
|
|
391
|
+
end
|
|
392
|
+
end.uniq.freeze
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
@hook_keys = {}
|
|
396
|
+
|
|
397
|
+
# The frozen `method:Name.hook` negative keys of `name`'s {Relevance::HOOKS}, built once per name.
|
|
398
|
+
def self.hook_keys(name)
|
|
399
|
+
@hook_keys[name] ||= Relevance::HOOKS.map { |hook| "method:#{name}.#{hook}".freeze }.freeze
|
|
400
|
+
end
|
|
401
|
+
|
|
271
402
|
def record_head(scope, start, side)
|
|
272
403
|
ResolutionChain.record_class(scope, @root) unless start.zero? && @entries.first&.name == @root
|
|
273
404
|
start.times { |index| ResolutionChain.record_entry(scope, @entries[index], side) }
|
|
@@ -397,7 +528,8 @@ module Rigor
|
|
|
397
528
|
@discovery.discovered_superclasses.key?(name) || @discovery.discovered_includes.key?(name) ||
|
|
398
529
|
@discovery.discovered_classes.key?(name)
|
|
399
530
|
when :arity
|
|
400
|
-
@scope.known_user_class?(name) ||
|
|
531
|
+
@scope.known_user_class?(name) ||
|
|
532
|
+
DiscoveryIndex.declares_surface?(@discovery.discovered_parameter_envelopes[name])
|
|
401
533
|
else
|
|
402
534
|
@scope.known_user_class?(name)
|
|
403
535
|
end
|
|
@@ -601,7 +733,8 @@ module Rigor
|
|
|
601
733
|
# The chain for `root`, carrying its retro world when the skip rule skipped anything on the way.
|
|
602
734
|
def chain(root, side)
|
|
603
735
|
entries, starts, classes, forks, marks, retro_ok = lin_for(root, side)
|
|
604
|
-
|
|
736
|
+
order_marked = marks.any? { |mark| mark.kind != :extend_hook }
|
|
737
|
+
retro = build_retro(root, side) if forks == 1 && retro_ok && !order_marked && !@retro
|
|
605
738
|
cut_at = cut_position(entries)
|
|
606
739
|
return whole_chain(root, side, entries, starts, classes, [forks, marks], retro) if cut_at.nil?
|
|
607
740
|
|
|
@@ -701,6 +834,7 @@ module Rigor
|
|
|
701
834
|
def compute_instance(name, depth)
|
|
702
835
|
# `discovered_includes` already carries the prepended names, so it counts every instance-side edge once.
|
|
703
836
|
mark_unsettled(name, :include, (@discovery.discovered_includes[name] || EMPTY).size)
|
|
837
|
+
@frames.last.add_marks(mark_for(name, :extend_hook, WILDCARD_LIST, false)) if extend_hook_taint?(name)
|
|
704
838
|
entries = [project_entry(name, :instance)]
|
|
705
839
|
super_lin = superclass_lin(name, :instance, depth)
|
|
706
840
|
entries.concat(super_lin[0])
|
|
@@ -743,6 +877,20 @@ module Rigor
|
|
|
743
877
|
multi)].freeze
|
|
744
878
|
end
|
|
745
879
|
|
|
880
|
+
WILDCARD_LIST = ["*"].freeze # Relevance::WILDCARD, which loads after this file
|
|
881
|
+
private_constant :WILDCARD_LIST
|
|
882
|
+
|
|
883
|
+
# #1730 — whether a module `name` extends has a hook that may mix into `name`'s instance side or its
|
|
884
|
+
# includers' ({ResolutionChain.extend_reshapes_instance?}). Ruby runs it at the `extend`, so the mixin is
|
|
885
|
+
# not a fact of the tables and `name`'s instance side is unpositioned. A spelling no project module
|
|
886
|
+
# answers taints nothing: the project cannot see its hooks.
|
|
887
|
+
def extend_hook_taint?(name)
|
|
888
|
+
(@discovery.discovered_extends[name] || EMPTY).any? do |raw|
|
|
889
|
+
resolved = @resolver.resolve_one(name, raw)
|
|
890
|
+
!resolved.nil? && ResolutionChain.extend_reshapes_instance?(@scope, resolved)
|
|
891
|
+
end
|
|
892
|
+
end
|
|
893
|
+
|
|
746
894
|
def multi_file?(name)
|
|
747
895
|
sites = @discovery.discovered_class_sources[name]
|
|
748
896
|
!sites.nil? && sites.size >= 2
|
|
@@ -866,14 +1014,14 @@ module Rigor
|
|
|
866
1014
|
|
|
867
1015
|
def project_entry(name, side)
|
|
868
1016
|
by_side = (@bucket[:interned][side] ||= {})
|
|
869
|
-
by_side[name] ||= Entry.new(name, side, nil, nil, false, name.to_s.split("::").last.freeze)
|
|
1017
|
+
by_side[name] ||= Entry.new(name, side, nil, nil, false, name.to_s.split("::").last.freeze, nil)
|
|
870
1018
|
end
|
|
871
1019
|
|
|
872
1020
|
def external_entry(owner, raw, side, superclass_edge)
|
|
873
1021
|
by_edge = ((@bucket[:externals][side] ||= {})[superclass_edge] ||= {})
|
|
874
1022
|
by_owner = (by_edge[owner] ||= {})
|
|
875
1023
|
by_owner[raw] ||= Entry.new(nil, side, raw, @scope.ancestor_name_candidates(owner, raw).freeze,
|
|
876
|
-
superclass_edge, nil)
|
|
1024
|
+
superclass_edge, nil, owner)
|
|
877
1025
|
end
|
|
878
1026
|
end
|
|
879
1027
|
private_constant :Builder
|