rigortype 0.4.4 → 0.4.6
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 +5 -5
- data/docs/handbook/11-sig-gen.md +100 -18
- data/docs/manual/02-cli-reference.md +85 -16
- data/docs/manual/03-configuration.md +12 -9
- data/docs/manual/04-diagnostics.md +17 -6
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +2 -1
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +17 -2
- data/docs/manual/12-caching.md +8 -1
- data/docs/manual/18-removing-dead-code.md +18 -7
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +30 -0
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +102 -1
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +30 -9
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +142 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +115 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +253 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +3 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +64 -4
- data/lib/rigor/analysis/check_rules.rb +319 -21
- data/lib/rigor/analysis/dependency_recorder.rb +11 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/edit_replay.rb +67 -0
- data/lib/rigor/analysis/incremental.rb +86 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +336 -46
- data/lib/rigor/analysis/path_expansion.rb +44 -1
- data/lib/rigor/analysis/reachability/graph.rb +90 -13
- data/lib/rigor/analysis/reachability/scan.rb +279 -39
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +22 -5
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +11 -3
- data/lib/rigor/analysis/runner/pool_coordinator.rb +82 -16
- data/lib/rigor/analysis/runner/project_pre_passes.rb +45 -3
- data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner.rb +222 -111
- data/lib/rigor/analysis/severity_stamp.rb +11 -0
- data/lib/rigor/analysis/worker_session.rb +24 -1
- data/lib/rigor/cache/descriptor.rb +40 -7
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +49 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/check_command.rb +93 -4
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/diff_command.rb +2 -1
- data/lib/rigor/cli/docs_command.rb +3 -2
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +3 -2
- data/lib/rigor/cli/lens_command.rb +5 -5
- data/lib/rigor/cli/mcp_command.rb +2 -1
- data/lib/rigor/cli/options.rb +20 -3
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +7 -1
- data/lib/rigor/cli/plugins_renderer.rb +24 -2
- data/lib/rigor/cli/show_bleedingedge_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +73 -13
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/type_of_project_scope.rb +27 -0
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +72 -16
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +43 -7
- data/lib/rigor/effects/signature_sources.rb +3 -1
- data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
- data/lib/rigor/environment/gated_signature_guard.rb +2 -1
- data/lib/rigor/environment/gated_signature_plan.rb +2 -1
- data/lib/rigor/environment/installed_gem_set.rb +2 -1
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
- data/lib/rigor/environment/rbs_loader.rb +136 -7
- data/lib/rigor/environment/required_features.rb +136 -29
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment.rb +4 -2
- data/lib/rigor/inference/block_call_timing.rb +7 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/callee_mutation_chain.rb +115 -0
- data/lib/rigor/inference/captured_locals.rb +44 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/deep_erasure.rb +138 -0
- data/lib/rigor/inference/def_return_typer.rb +159 -9
- data/lib/rigor/inference/definer_resolution.rb +101 -20
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +97 -18
- data/lib/rigor/inference/global_write_census.rb +36 -1
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/ivar_writer_census.rb +195 -0
- data/lib/rigor/inference/loop_value_record.rb +53 -0
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/match_rebinding/frame.rb +19 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +278 -42
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +53 -20
- data/lib/rigor/inference/method_dispatcher.rb +113 -19
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +149 -42
- data/lib/rigor/inference/mutation_widening.rb +5 -2
- data/lib/rigor/inference/narrowing.rb +116 -11
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
- data/lib/rigor/inference/project_patched_methods.rb +168 -4
- data/lib/rigor/inference/project_patched_mixins.rb +162 -0
- data/lib/rigor/inference/project_patched_scanner.rb +51 -12
- data/lib/rigor/inference/project_patched_visibility.rb +214 -0
- data/lib/rigor/inference/project_seed_tables.rb +121 -0
- data/lib/rigor/inference/project_yielder.rb +128 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
- data/lib/rigor/inference/reflective_ivar_write.rb +6 -6
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +9 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +162 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +1144 -197
- data/lib/rigor/inference/self_call_ivar_writes.rb +29 -12
- data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
- data/lib/rigor/inference/statement_evaluator.rb +1088 -213
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +72 -12
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/language_server/buffer_table.rb +6 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +20 -6
- data/lib/rigor/language_server/project_context.rb +59 -3
- data/lib/rigor/language_server/server.rb +5 -3
- data/lib/rigor/lens/contract_reader.rb +45 -2
- data/lib/rigor/lens/document.rb +2 -1
- data/lib/rigor/lens/file_reader.rb +12 -2
- data/lib/rigor/lens/members.rb +16 -2
- data/lib/rigor/lens/run.rb +34 -7
- data/lib/rigor/mcp/server.rb +11 -3
- data/lib/rigor/plugin/base.rb +29 -13
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/plugin/manifest.rb +29 -3
- data/lib/rigor/plugin/registry.rb +38 -0
- data/lib/rigor/protection/discovery_seed.rb +64 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +36 -0
- data/lib/rigor/scope/discovery_index.rb +5 -0
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
- data/lib/rigor/scope/resolution_chain.rb +40 -5
- data/lib/rigor/scope.rb +59 -7
- data/lib/rigor/sig_gen/alias_index.rb +2 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +465 -49
- data/lib/rigor/sig_gen/layout_index.rb +5 -2
- data/lib/rigor/sig_gen/observation_collector.rb +141 -12
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +16 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- data/lib/rigor/sig_gen/write_reporter.rb +203 -0
- data/lib/rigor/sig_gen/write_result.rb +3 -1
- data/lib/rigor/sig_gen/writer.rb +5 -3
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/combinator.rb +4 -3
- data/lib/rigor/type/constant.rb +54 -3
- data/lib/rigor/type/hash_shape.rb +14 -9
- data/lib/rigor/type/struct_instance.rb +3 -2
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +61 -44
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +20 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +13 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +38 -9
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +4 -2
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +3 -2
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +6 -0
- data/sig/rigor/plugin/additional_initializer.rbs +5 -5
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +10 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +34 -52
- data/sig/rigor/type.rbs +0 -6
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +32 -1
|
@@ -209,7 +209,13 @@ module Rigor
|
|
|
209
209
|
def table_digest(project_scan)
|
|
210
210
|
payload = {
|
|
211
211
|
"synthetic" => project_scan.synthetic_method_index.to_h,
|
|
212
|
-
"patched" => patched_rows(project_scan.project_patched_methods)
|
|
212
|
+
"patched" => patched_rows(project_scan.project_patched_methods),
|
|
213
|
+
"patched_mixins" => project_scan.project_patched_methods.mixins.map do |mixin|
|
|
214
|
+
mixin.to_h.values.map(&:to_s)
|
|
215
|
+
end,
|
|
216
|
+
"patched_overrides" => project_scan.project_patched_methods.overrides.map do |key, visibility|
|
|
217
|
+
[*key, visibility].map(&:to_s)
|
|
218
|
+
end
|
|
213
219
|
}
|
|
214
220
|
Digest::SHA256.hexdigest(JSON.generate(payload))
|
|
215
221
|
rescue StandardError
|
|
@@ -221,7 +227,7 @@ module Rigor
|
|
|
221
227
|
def patched_rows(patched)
|
|
222
228
|
patched.by_key.map do |(class_name, method_name, kind), entry|
|
|
223
229
|
[class_name.to_s, method_name.to_s, kind.to_s, entry.source_path.to_s, entry.source_line,
|
|
224
|
-
entry.return_type&.describe(:short).to_s, entry.alias_of.to_s]
|
|
230
|
+
entry.return_type&.describe(:short).to_s, entry.alias_of.to_s, entry.visibility.to_s]
|
|
225
231
|
end.sort
|
|
226
232
|
end
|
|
227
233
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# `Dir.glob(File.join(root, pattern))` with `root` read as a literal path (#1956, #1959).
|
|
5
|
+
#
|
|
6
|
+
# Joining a directory into the pattern makes `Dir.glob` read the directory's own characters as glob syntax:
|
|
7
|
+
# a project at `ds [x]` or `ds{a}` matches nothing, `ds*` matches its siblings, `fx[1]` matches `fx1`, and
|
|
8
|
+
# a directory literally named `{..,zz}` expands into the parent tree. Each glob metacharacter of `root` is
|
|
9
|
+
# backslash-escaped before the join, so `root` matches only itself while `pattern` keeps exactly the meaning the
|
|
10
|
+
# joined spelling gave it: a leading `/` collapses into the join (`/etc/*` reads `root/etc/*`), and so does one
|
|
11
|
+
# inside a brace alternative (`{/etc,lib}/*` reads `root/etc/*` and `root/lib/*`). `Dir.glob(pattern, base:)`
|
|
12
|
+
# would not do that: it reads an absolute pattern, or an absolute alternative, outside `base`. A root without
|
|
13
|
+
# metacharacters is joined unchanged, so the results are the joined glob's strings in its order, `root` kept as
|
|
14
|
+
# given (relative, symlinked or with a trailing `/`).
|
|
15
|
+
#
|
|
16
|
+
# `literal(name)` is the same escape for a single name (a file's basename, a configured path) that is joined into
|
|
17
|
+
# a pattern as a literal: `literal("a[1].rb")` matches `a[1].rb` and not `a1.rb`.
|
|
18
|
+
module RootedGlob
|
|
19
|
+
METACHARACTERS = /[\[\]{}*?\\]/
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# @param root — a directory path (String or Pathname), taken literally
|
|
24
|
+
# @param pattern — a `Dir.glob` pattern relative to `root`
|
|
25
|
+
# @return the matches spelled under `root`, in `Dir.glob` order; empty when `root` is not a directory
|
|
26
|
+
def glob(root, pattern)
|
|
27
|
+
Dir.glob(File.join(literal(root), pattern.to_s))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param name — a file or directory name, taken literally
|
|
31
|
+
# @return the pattern that matches exactly `name` under `Dir.glob`, with its metacharacters escaped
|
|
32
|
+
def literal(name)
|
|
33
|
+
name.to_s.gsub(METACHARACTERS, "\\\\\\0")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -231,6 +231,11 @@ module Rigor
|
|
|
231
231
|
# (the meta-new walk does not route `Parent` through ancestor discovery), so a reader that must place `K` in a
|
|
232
232
|
# hierarchy reads its superclass as unknown, as it reads the `nil` the table records for `class K < <expr>`.
|
|
233
233
|
ENVELOPE_META_SUPERCLASS_MARK = :"<meta-superclass>"
|
|
234
|
+
# Issue #1979 — the kind of a `[ENVELOPE_CONSTRUCTED_KIND, constant_name]` bucket entry: `class K < <expr>` whose
|
|
235
|
+
# superclass expression is a core constructor call (`Data.define(...)`, `Struct.new(...)`, `Class.new(...)`)
|
|
236
|
+
# naming that constant, as the receiver or as `Class.new`'s single bare-constant argument (`Object` for none).
|
|
237
|
+
# Read only by the #1596 proof's subclass walk (`RefinedDispatch::OwnMethod.each_subclass`).
|
|
238
|
+
ENVELOPE_CONSTRUCTED_KIND = :constructed_from
|
|
234
239
|
ENVELOPE_MODULE_MARK = :"<module>"
|
|
235
240
|
ENVELOPE_DYNAMIC_MARK = :"<dynamic>"
|
|
236
241
|
ENVELOPE_OBJECT_EXTENDED_MARK = :"<object-extended>"
|
|
@@ -84,6 +84,20 @@ module Rigor
|
|
|
84
84
|
false
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
# {.external_lacks?} on the side an entry stands for: an external superclass on a singleton chain
|
|
88
|
+
# (`:singleton`, its whole tail) is asked for a singleton method, an extended module (`:instance`) and every
|
|
89
|
+
# instance-side entry for an instance method.
|
|
90
|
+
def external_lacks_on?(scope, candidates, side, name)
|
|
91
|
+
return external_lacks?(scope, candidates, name) if side == :instance
|
|
92
|
+
|
|
93
|
+
known = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: scope) }
|
|
94
|
+
return false if known.nil?
|
|
95
|
+
|
|
96
|
+
Rigor::Reflection.singleton_method_definition(known, name, scope: scope).nil?
|
|
97
|
+
rescue StandardError
|
|
98
|
+
false
|
|
99
|
+
end
|
|
100
|
+
|
|
87
101
|
# One verdict's working state: the scope, the resolver, and the edges the tests read.
|
|
88
102
|
class Context
|
|
89
103
|
attr_reader :mark, :edges
|
|
@@ -300,17 +300,24 @@ module Rigor
|
|
|
300
300
|
# `Kernel` or `Enumerable` on any reopening. So each candidate name files the negative method edge of every
|
|
301
301
|
# {Relevance::HOOKS} name on its singleton side (`method:Comparable.included`, the key a new `def
|
|
302
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.
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
#
|
|
303
|
+
# module is declared its sites carry later edits and the removal. #1661 — each name also files `mixin:<name>`,
|
|
304
|
+
# which `IncrementalSession#mark_affected` fires when the module's unpositioned mixin edges move: a new file
|
|
305
|
+
# that taints it (`module Comparable; include(*MODS); end`) without a hook. The key is the qualified candidate,
|
|
306
|
+
# the spelling the chain resolves a project entry by. The unpositioned table may key the owner by a relative
|
|
307
|
+
# spelling (`Constants` inside `class File`), so the producer also fires every recorded `mixin:` key ending in
|
|
308
|
+
# `::<owner>` (`IncrementalSession#mixin_keys`); a taint on a project `Foo::Base` still re-checks no reader of
|
|
309
|
+
# an external `Lib::Base`. An ambiguous spelling has no candidate and files none: no project module can resolve
|
|
310
|
+
# it, so no taint can mark it. The edges are a function of the chain alone, so a consumer files them once per
|
|
311
|
+
# chain ({Analysis::DependencyRecorder.file_chain_once}, #1590). A hook written another way — an instance `def
|
|
312
|
+
# included` in a module extended onto the external, a `define_singleton_method(:included)` — satisfies no such
|
|
313
|
+
# key and is not seen.
|
|
307
314
|
def record_hooks(scope)
|
|
308
315
|
externals = (@memo[:externals] ||= @entries.select(&:external?).freeze)
|
|
309
316
|
return if externals.empty?
|
|
310
317
|
|
|
311
318
|
Analysis::DependencyRecorder.file_chain_once(self, :hooks, scope.discovery) do
|
|
312
319
|
externals.each do |entry|
|
|
313
|
-
entry.candidates.each { |name| Analysis::DependencyRecorder.read_keys(ResolutionChain.
|
|
320
|
+
entry.candidates.each { |name| Analysis::DependencyRecorder.read_keys(ResolutionChain.external_keys(name)) }
|
|
314
321
|
end
|
|
315
322
|
end
|
|
316
323
|
end
|
|
@@ -393,12 +400,18 @@ module Rigor
|
|
|
393
400
|
end
|
|
394
401
|
|
|
395
402
|
@hook_keys = {}
|
|
403
|
+
@external_keys = {}
|
|
396
404
|
|
|
397
405
|
# The frozen `method:Name.hook` negative keys of `name`'s {Relevance::HOOKS}, built once per name.
|
|
398
406
|
def self.hook_keys(name)
|
|
399
407
|
@hook_keys[name] ||= Relevance::HOOKS.map { |hook| "method:#{name}.#{hook}".freeze }.freeze
|
|
400
408
|
end
|
|
401
409
|
|
|
410
|
+
# {hook_keys} plus the `mixin:<name>` key an external entry files for its unpositioned mixins (#1661).
|
|
411
|
+
def self.external_keys(name)
|
|
412
|
+
@external_keys[name] ||= (hook_keys(name) + ["mixin:#{name}".freeze]).freeze
|
|
413
|
+
end
|
|
414
|
+
|
|
402
415
|
def record_head(scope, start, side)
|
|
403
416
|
ResolutionChain.record_class(scope, @root) unless start.zero? && @entries.first&.name == @root
|
|
404
417
|
start.times { |index| ResolutionChain.record_entry(scope, @entries[index], side) }
|
|
@@ -440,6 +453,28 @@ module Rigor
|
|
|
440
453
|
@entries[@level_starts[index]...finish]
|
|
441
454
|
end
|
|
442
455
|
|
|
456
|
+
# Issue #1624 — whether the chain's last class has a superclass the tables cannot name, so ancestors the chain
|
|
457
|
+
# does not list follow it where a chain otherwise ends in the implicit `Object`: a superclass written as an
|
|
458
|
+
# expression (`class K < Struct.new(:a)`, `< Data.define(:a)`, `< Class.new(Base)`, `< pick_base`), which the
|
|
459
|
+
# superclass table records as `nil`, or a class a constant write creates (`K = Class.new(Base)`,
|
|
460
|
+
# `K = Struct.new(:a)`, `K = Data.define(:a)`), which has no superclass entry. The constructor's methods
|
|
461
|
+
# (`members`, the member readers, a block's `def`s) and everything it inherits live in that unlisted part.
|
|
462
|
+
# Every fact read here is filed under the last class's class edge, which a read reaching the tail has passed.
|
|
463
|
+
def opaque_tail?(scope)
|
|
464
|
+
@memo.fetch(:opaque_tail) do
|
|
465
|
+
last = level_count.positive? ? @level_classes[level_count - 1] : nil
|
|
466
|
+
@memo[:opaque_tail] = !last.nil? && ResolutionChain.opaque_superclass?(scope, last)
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def self.opaque_superclass?(scope, name)
|
|
471
|
+
superclasses = scope.discovery.discovered_superclasses
|
|
472
|
+
return superclasses[name].nil? if superclasses.key?(name)
|
|
473
|
+
|
|
474
|
+
scope.discovery.discovered_parameter_envelopes[name]&.key?(DiscoveryIndex::ENVELOPE_META_SUPERCLASS_MARK) ||
|
|
475
|
+
!(scope.struct_member_layout(name) || scope.data_member_layout(name)).nil?
|
|
476
|
+
end
|
|
477
|
+
|
|
443
478
|
# Each complete level up to the first superclass the project does not declare, as `[class_name,
|
|
444
479
|
# modules, externals]`: the level's class, the project modules around it (each once), and the candidate
|
|
445
480
|
# lists of the ancestors in it the project does not declare — the shape `call.wrong-arity` reads.
|
data/lib/rigor/scope.rb
CHANGED
|
@@ -1213,13 +1213,28 @@ module Rigor
|
|
|
1213
1213
|
# wrong-arity rules consult this map to suppress diagnostics for methods the user has defined dynamically,
|
|
1214
1214
|
# even when no RBS sig describes them.
|
|
1215
1215
|
# A name defined on both sides of one class records {DiscoveryIndex::METHOD_KIND_BOTH} and matches either kind.
|
|
1216
|
+
#
|
|
1217
|
+
# Issue #2121 — a hit is an ADR-46 read. The table carries what no `def` symbol edge covers — an `attr_*`, an
|
|
1218
|
+
# `alias` or a `define_method` (a cross-file `def` is withheld from it) — so an answer that read a hit records
|
|
1219
|
+
# the class edge to the files declaring `class_name` and the name edge `defines:<method_name>`: deleting the
|
|
1220
|
+
# `attr_accessor` re-checks the caller it silenced, whether or not the deleting file declares the class. A miss
|
|
1221
|
+
# records nothing here; the readers that act on one record their own negative edges.
|
|
1216
1222
|
def discovered_method?(class_name, method_name, kind)
|
|
1217
1223
|
table = @discovery.discovered_methods[class_name.to_s]
|
|
1218
1224
|
return false unless table
|
|
1219
1225
|
|
|
1220
1226
|
recorded = table[method_name.to_sym]
|
|
1221
|
-
recorded == kind || recorded == DiscoveryIndex::METHOD_KIND_BOTH
|
|
1227
|
+
return false unless recorded == kind || recorded == DiscoveryIndex::METHOD_KIND_BOTH
|
|
1228
|
+
|
|
1229
|
+
record_discovered_method(class_name, method_name) if Analysis::DependencyRecorder.active?
|
|
1230
|
+
true
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
def record_discovered_method(class_name, method_name)
|
|
1234
|
+
record_class_dependency(class_name)
|
|
1235
|
+
Analysis::DependencyRecorder.read_name(:defines, method_name)
|
|
1222
1236
|
end
|
|
1237
|
+
private :record_discovered_method
|
|
1223
1238
|
|
|
1224
1239
|
# ADR-34 § "Decision" — predicate identifying a toplevel-shaped scope (no enclosing `class` / `module` body).
|
|
1225
1240
|
# True at the top of a file AND inside a top-level `def` body (since toplevel defs leave `self_type` nil per
|
|
@@ -1410,6 +1425,27 @@ module Rigor
|
|
|
1410
1425
|
call_node)
|
|
1411
1426
|
end
|
|
1412
1427
|
|
|
1428
|
+
# Issue #1569 — whether `def_node`, written in this file, is a module function of `class_name`: a `:both`
|
|
1429
|
+
# deferred-range row over the def (a bare `module_function` before it, or `module_function def x`;
|
|
1430
|
+
# `ModuleFunctionState.prescan_deferred`), or a `:singleton` row for the name after the def
|
|
1431
|
+
# (`module_function :x`) whose singleton copy is this def (`ModuleFunctionState.each_singleton_copy`).
|
|
1432
|
+
def module_function_def?(class_name, def_node)
|
|
1433
|
+
path = source_path
|
|
1434
|
+
return false if path.nil?
|
|
1435
|
+
|
|
1436
|
+
ranges = discovered_deferred_ranges[path]
|
|
1437
|
+
return false if ranges.nil?
|
|
1438
|
+
|
|
1439
|
+
start = def_node.location.start_offset
|
|
1440
|
+
ranges.any? do |(row_start, _finish, name, kind, owner)|
|
|
1441
|
+
next false unless name == def_node.name && owner == class_name.to_s
|
|
1442
|
+
|
|
1443
|
+
(kind == :both && row_start == start) ||
|
|
1444
|
+
(kind == :singleton && row_start > start &&
|
|
1445
|
+
singleton_def_for(class_name, name)&.location&.start_offset == start)
|
|
1446
|
+
end
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1413
1449
|
# Shared ordering half of the two `*_def_shadows_call?` predicates, over this file's
|
|
1414
1450
|
# `discovered_deferred_ranges` (issue #1097). `exists` — a recorded `"path:line"` site or the
|
|
1415
1451
|
# method-existence table — says a def of `method_name`/`kind` on `class_name` is known at all; a
|
|
@@ -1527,7 +1563,8 @@ module Rigor
|
|
|
1527
1563
|
#
|
|
1528
1564
|
# This is EVIDENCE FOR a singleton ancestor, never against one: the walk sees only what a constant
|
|
1529
1565
|
# argument to a receiverless `extend` (or, since #915, a receiverless `include` / `prepend` inside a
|
|
1530
|
-
# `class << self` body) written inside a declaration body spells,
|
|
1566
|
+
# `class << self` body) written inside a declaration body spells, or, since #2094, `Widget.extend(M)` and
|
|
1567
|
+
# `Widget.singleton_class.include(M)` with a constant receiver, so a runtime `Widget.extend(m)` and an
|
|
1531
1568
|
# `extend` in a file outside the analysed set are both absent from it, as is an `extend` declared only
|
|
1532
1569
|
# in RBS — that one reaches the same consumer through `Environment#singleton_extended_modules` instead.
|
|
1533
1570
|
# Its one consumer ({Inference::Narrowing.narrow_class}) therefore reads it
|
|
@@ -1905,8 +1942,9 @@ module Rigor
|
|
|
1905
1942
|
# declaration-level fact of the whole file, which is also the granularity
|
|
1906
1943
|
# `ScopeIndexer#append_constant_signature` moves the declaration signature at.
|
|
1907
1944
|
#
|
|
1908
|
-
# `constant_sources` is seeded
|
|
1909
|
-
# is empty and this is one Hash miss
|
|
1945
|
+
# `constant_sources` is seeded when dependency recording is on and in the editor publish's discovery seed
|
|
1946
|
+
# (ADR-124), where the recorder is inactive; elsewhere the table is empty and this is one Hash miss. The caller
|
|
1947
|
+
# gates on the recorder being active.
|
|
1910
1948
|
def record_constant_dependency(name)
|
|
1911
1949
|
sites = @discovery.constant_sources[name]
|
|
1912
1950
|
return if sites.nil?
|
|
@@ -1917,8 +1955,15 @@ module Rigor
|
|
|
1917
1955
|
# Issue #992 — one class's `discovered_parameter_envelopes` bucket (`{[kind, method_name] => envelope}` plus
|
|
1918
1956
|
# the class-wide marks), or an empty Hash. Records the ADR-46 class edge first: a verdict read off the
|
|
1919
1957
|
# bucket depends on every file that declares the class, and a `memoize :f` added to one of them moves it.
|
|
1958
|
+
#
|
|
1959
|
+
# Issue #1833 — and the name edge `mark:<last segment>`: a file that does not declare the class may still mark it
|
|
1960
|
+
# (`x.singleton_class.prepend(Comparable)`, `o.extend(Helper)` in a method body, a `refine` block), and
|
|
1961
|
+
# `IncrementalSession#mark_affected` fires the key when such a mark appears or vanishes.
|
|
1920
1962
|
def parameter_envelopes_of(class_name)
|
|
1921
|
-
|
|
1963
|
+
if Analysis::DependencyRecorder.active?
|
|
1964
|
+
record_class_dependency(class_name)
|
|
1965
|
+
Analysis::DependencyRecorder.read_last_segment(:mark, class_name.to_s)
|
|
1966
|
+
end
|
|
1922
1967
|
@discovery.discovered_parameter_envelopes[class_name.to_s] || EMPTY_PARAMETER_ENVELOPES
|
|
1923
1968
|
end
|
|
1924
1969
|
|
|
@@ -1930,11 +1975,18 @@ module Rigor
|
|
|
1930
1975
|
# a wrapping `private def` / `private attr_reader` call, else the surrounding `private` / `protected` / `public`
|
|
1931
1976
|
# modifier state, plus any post-hoc `private :name, ...` named-argument calls. Consumed by the
|
|
1932
1977
|
# `def.method-visibility-mismatch` rule so explicit-non-self calls to a private method surface a diagnostic.
|
|
1933
|
-
|
|
1978
|
+
# `singleton: true` reads the class's singleton slot for the name (`[:singleton, name]`).
|
|
1979
|
+
#
|
|
1980
|
+
# Issue #2134 — every answer is an ADR-46 read of the slot, so it records `visibility:<class>#<method_name>`
|
|
1981
|
+
# whichever way it answers ({Analysis::DependencyRecorder.read_visibility}). A `private :foo` can be written from
|
|
1982
|
+
# a reopening or a `class_eval` block in a file the reader has no other edge to, and adding or removing it must
|
|
1983
|
+
# re-check the reader.
|
|
1984
|
+
def discovered_method_visibility(class_name, method_name, singleton: false)
|
|
1985
|
+
Analysis::DependencyRecorder.read_visibility(class_name, method_name) if Analysis::DependencyRecorder.active?
|
|
1934
1986
|
table = @discovery.discovered_method_visibilities[class_name.to_s]
|
|
1935
1987
|
return nil unless table
|
|
1936
1988
|
|
|
1937
|
-
table[method_name.to_sym]
|
|
1989
|
+
table[singleton ? [:singleton, method_name.to_sym] : method_name.to_sym]
|
|
1938
1990
|
end
|
|
1939
1991
|
|
|
1940
1992
|
# Closes the "`params[:f] ||= []; params[:f] << x`" precision gap (ROADMAP § Type-language / engine —
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../inference/rbs_type_translator"
|
|
4
4
|
require_relative "type_elaborator"
|
|
5
|
+
require_relative "../rooted_glob"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module SigGen
|
|
@@ -101,7 +102,7 @@ module Rigor
|
|
|
101
102
|
dir = path.is_a?(Pathname) ? path : Pathname(path.to_s)
|
|
102
103
|
next [] unless dir.directory?
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
RootedGlob.glob(dir, "**/*.rbs").map { |p| File.expand_path(p) }
|
|
105
106
|
end.to_set
|
|
106
107
|
end
|
|
107
108
|
|
|
@@ -60,7 +60,10 @@ module Rigor
|
|
|
60
60
|
inline_differs: "sig.skipped.inline-differs",
|
|
61
61
|
# #1676 — a plain top-level `def` defines a private `Object` method and has no class to write a
|
|
62
62
|
# declaration under, so it is reported rather than silently dropped. Whether to emit it stays open.
|
|
63
|
-
top_level_def: "sig.skipped.top-level-def"
|
|
63
|
+
top_level_def: "sig.skipped.top-level-def",
|
|
64
|
+
# #1968 — a bare `public` / `private` / `protected` that may not run (in control flow or a block) sits
|
|
65
|
+
# between a bare `module_function` and the def, so whether the def is a module function is unknown.
|
|
66
|
+
uncertain_module_function: "sig.skipped.uncertain-module-function"
|
|
64
67
|
}.freeze
|
|
65
68
|
end
|
|
66
69
|
end
|