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
|
@@ -6,6 +6,7 @@ require_relative "../reflection"
|
|
|
6
6
|
require_relative "../source/node_walker"
|
|
7
7
|
require_relative "../source/constant_path"
|
|
8
8
|
require_relative "../inference/singleton_object_constant"
|
|
9
|
+
require_relative "../inference/object_mixins"
|
|
9
10
|
require_relative "shadow_harness"
|
|
10
11
|
require_relative "../type"
|
|
11
12
|
require_relative "diagnostic"
|
|
@@ -29,6 +30,7 @@ require_relative "check_rules/self_closedness_scanner"
|
|
|
29
30
|
require_relative "check_rules/source_arity"
|
|
30
31
|
require_relative "check_rules/lexical_method_sites"
|
|
31
32
|
require_relative "check_rules/special_global_setters"
|
|
33
|
+
require_relative "check_rules/ruby_deprecations"
|
|
32
34
|
|
|
33
35
|
module Rigor
|
|
34
36
|
module Analysis
|
|
@@ -66,6 +68,17 @@ module Rigor
|
|
|
66
68
|
# resolution chain's.
|
|
67
69
|
VISIBILITY_RANK = { public: 2, protected: 1, private: 0 }.freeze
|
|
68
70
|
|
|
71
|
+
# ADR-35 WD10 (issue #1716) — the object-lifecycle hooks the three override rules skip. `initialize` is
|
|
72
|
+
# reached through `Class#new` on the class the caller names, and the copy hooks are handed an instance
|
|
73
|
+
# of the receiver's own class by `dup` / `clone`, so neither is called with the PARENT's declared
|
|
74
|
+
# arguments through a parent-typed reference; `new` / `dup` / `clone` discard all four returns.
|
|
75
|
+
LIFECYCLE_HOOKS = %i[initialize initialize_copy initialize_dup initialize_clone].freeze
|
|
76
|
+
|
|
77
|
+
# The names CRuby makes private on every non-singleton `def` whatever section it is written in
|
|
78
|
+
# (`vm_method.c` `rb_method_entry_make`), so a source-discovered visibility for them is not the
|
|
79
|
+
# runtime one and `def.override-visibility-reduced` cannot prove a reduction.
|
|
80
|
+
IMPLICITLY_PRIVATE_METHODS = (LIFECYCLE_HOOKS + %i[respond_to_missing?]).freeze
|
|
81
|
+
|
|
69
82
|
# Resolves a user-supplied rule token (`undefined-method`,
|
|
70
83
|
# `call.undefined-method`, or the family wildcard `call`)
|
|
71
84
|
# to the set of canonical rule identifiers it disables.
|
|
@@ -74,6 +87,7 @@ module Rigor
|
|
|
74
87
|
def self.resolve_rule_token(token)
|
|
75
88
|
return nil if token == "all"
|
|
76
89
|
return [LEGACY_RULE_ALIASES.fetch(token)] if LEGACY_RULE_ALIASES.key?(token)
|
|
90
|
+
return PRE_EVAL_RULES if token == "pre-eval"
|
|
77
91
|
return ALL_RULES.select { |r| r.start_with?("#{token}.") } if RULE_FAMILIES.include?(token)
|
|
78
92
|
|
|
79
93
|
ALL_RULES.include?(token) ? [token] : []
|
|
@@ -96,8 +110,11 @@ module Rigor
|
|
|
96
110
|
# direct caller with no plugin walk, e.g. a unit test), the standalone
|
|
97
111
|
# {RuleWalk} walk runs here instead, so `diagnose` stays correct
|
|
98
112
|
# without the converged path.
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
#
|
|
114
|
+
# @param stated_runtime_ruby — {Configuration#stated_runtime_ruby}: the explicit `target_ruby`, or nil. Only
|
|
115
|
+
# {RubyDeprecations} reads it, and it walks the file only when the stated Ruby deprecates something.
|
|
116
|
+
def diagnose(path:, root:, scope_index:, self_call_misses: [], comments: [], disabled_rules: [], # rubocop:disable Metrics/ParameterLists
|
|
117
|
+
node_collectors: nil, stated_runtime_ruby: nil)
|
|
101
118
|
collectors = node_collectors || run_node_collectors(path, root, scope_index)
|
|
102
119
|
diagnostics = collectors[:main_pass].results.dup
|
|
103
120
|
diagnostics.concat(self_undefined_method_diagnostics(path, self_call_misses, root, scope_index))
|
|
@@ -109,6 +126,9 @@ module Rigor
|
|
|
109
126
|
# `suppression.*` joins the list, which stays reportable inside a dead arm — a malformed
|
|
110
127
|
# `# rigor:disable` marker is an authoring error regardless of whether its code runs.
|
|
111
128
|
diagnostics = DeadVersionGuardArms.filter(diagnostics, root)
|
|
129
|
+
# The ADR-47 WD5 amendment (#1692) — joined after that filter: the deprecation rule reads each version guard
|
|
130
|
+
# against the stated Ruby itself, and the analyzer's own Ruby says nothing about where the call runs.
|
|
131
|
+
diagnostics.concat(RubyDeprecations.diagnostics(path, root, scope_index, stated_runtime_ruby))
|
|
112
132
|
# Suppression-marker validation (`suppression.*`) runs BEFORE the filter so its own diagnostics are
|
|
113
133
|
# suppressible like any other rule — `# rigor:disable suppression.unknown-rule` on the offending
|
|
114
134
|
# comment's line works, with no regress (the token itself is known, so it never re-fires).
|
|
@@ -166,7 +186,7 @@ module Rigor
|
|
|
166
186
|
diagnostic = return_type_mismatch_diagnostic(path, node, scope_index)
|
|
167
187
|
return diagnostic if diagnostic.nil? || lexical_sites.nil?
|
|
168
188
|
|
|
169
|
-
lexical_sites.refinement_def?(node) ? nil : diagnostic
|
|
189
|
+
lexical_sites.refinements.refinement_def?(node) ? nil : diagnostic
|
|
170
190
|
end
|
|
171
191
|
|
|
172
192
|
# Constructs the fresh, unpopulated built-in collector set keyed by
|
|
@@ -178,7 +198,7 @@ module Rigor
|
|
|
178
198
|
# diagnostics carry it (ADR-53 B3c hosts it on the same walk).
|
|
179
199
|
def build_node_collectors(path, scope_index, root = nil)
|
|
180
200
|
eval_ranges = receiver_eval_block_ranges(root)
|
|
181
|
-
lexical_sites = LexicalMethodSites.new(root)
|
|
201
|
+
lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
|
|
182
202
|
main_pass = ->(node) { main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites) }
|
|
183
203
|
{
|
|
184
204
|
main_pass: MainPassCollector.new(main_pass),
|
|
@@ -252,6 +272,14 @@ module Rigor
|
|
|
252
272
|
collector.class.new(scope_index).collect(root)
|
|
253
273
|
end
|
|
254
274
|
|
|
275
|
+
# Issue #1673 — the in-effect refinements `Inference::ScopeIndexer` stamped for `root`, read off the root's scope
|
|
276
|
+
# so the rules answer from the query the typer reads; nil where the index holds no scope for it.
|
|
277
|
+
def stamped_refinements(scope_index, root)
|
|
278
|
+
return nil if root.nil? || scope_index.nil?
|
|
279
|
+
|
|
280
|
+
scope_index[root]&.discovery&.in_effect_refinements
|
|
281
|
+
end
|
|
282
|
+
|
|
255
283
|
# The former inline main pass, kept as the shadow oracle: walks the
|
|
256
284
|
# tree with `Source::NodeWalker.each` and accumulates the same
|
|
257
285
|
# per-node diagnostics in the same order {MainPassCollector} now
|
|
@@ -259,7 +287,7 @@ module Rigor
|
|
|
259
287
|
def main_pass_oracle(path, root, scope_index)
|
|
260
288
|
diagnostics = []
|
|
261
289
|
eval_ranges = receiver_eval_block_ranges(root)
|
|
262
|
-
lexical_sites = LexicalMethodSites.new(root)
|
|
290
|
+
lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
|
|
263
291
|
Source::NodeWalker.each(root) do |node|
|
|
264
292
|
diagnostics.concat(main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites))
|
|
265
293
|
end
|
|
@@ -284,15 +312,15 @@ module Rigor
|
|
|
284
312
|
def call_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil)
|
|
285
313
|
[
|
|
286
314
|
undefined_method_diagnostic(path, node, scope_index, lexical_sites),
|
|
287
|
-
unresolved_toplevel_diagnostic(path, node, scope_index, eval_ranges),
|
|
288
|
-
wrong_arity_diagnostic(path, node, scope_index),
|
|
289
|
-
argument_type_diagnostic(path, node, scope_index),
|
|
290
|
-
nil_receiver_diagnostic(path, node, scope_index),
|
|
315
|
+
unresolved_toplevel_diagnostic(path, node, scope_index, eval_ranges, lexical_sites),
|
|
316
|
+
wrong_arity_diagnostic(path, node, scope_index, lexical_sites),
|
|
317
|
+
argument_type_diagnostic(path, node, scope_index, lexical_sites),
|
|
318
|
+
nil_receiver_diagnostic(path, node, scope_index, lexical_sites),
|
|
291
319
|
dump_type_diagnostic(path, node, scope_index),
|
|
292
320
|
assert_type_diagnostic(path, node, scope_index),
|
|
293
|
-
always_raises_diagnostic(path, node, scope_index),
|
|
321
|
+
always_raises_diagnostic(path, node, scope_index, lexical_sites),
|
|
294
322
|
raise_non_exception_diagnostic(path, node, scope_index),
|
|
295
|
-
visibility_mismatch_diagnostic(path, node, scope_index)
|
|
323
|
+
visibility_mismatch_diagnostic(path, node, scope_index, lexical_sites)
|
|
296
324
|
].compact
|
|
297
325
|
end
|
|
298
326
|
|
|
@@ -316,6 +344,10 @@ module Rigor
|
|
|
316
344
|
# Class-level ivars (`@x = 1` outside any def, in the
|
|
317
345
|
# class body) are also skipped — they're a separate
|
|
318
346
|
# surface (`Module#@var`) the engine doesn't yet model.
|
|
347
|
+
# - A divergence the class's RBS declaration admits is withheld: when the ivar is declared (`@x: T`,
|
|
348
|
+
# or the slot an `attr_*` member implies) and that type accepts both the first write and the
|
|
349
|
+
# later one, the union is the author's intent. The declaration only silences here; checking
|
|
350
|
+
# writes against it is #1406.
|
|
319
351
|
def ivar_write_mismatch_diagnostics(path, ivar_writes)
|
|
320
352
|
ivar_writes.flat_map do |class_name, writes_by_ivar|
|
|
321
353
|
writes_by_ivar.flat_map do |ivar_name, writes|
|
|
@@ -427,11 +459,18 @@ module Rigor
|
|
|
427
459
|
writes[(canonical_index + 1)..].filter_map do |write|
|
|
428
460
|
other_class = ivar_class_for(write[:type])
|
|
429
461
|
next nil if other_class.nil? || other_class == "NilClass" || other_class == first_class
|
|
462
|
+
next nil if declaration_admits?(write[:declared], canonical[:type], write[:type])
|
|
430
463
|
|
|
431
464
|
build_ivar_write_mismatch_diagnostic(path, write[:node], class_name, ivar_name, first_class, other_class)
|
|
432
465
|
end
|
|
433
466
|
end
|
|
434
467
|
|
|
468
|
+
def declaration_admits?(declared, *write_types)
|
|
469
|
+
return false if declared.nil?
|
|
470
|
+
|
|
471
|
+
write_types.none? { |type| Inference::Acceptance.accepts(declared, type, mode: :gradual).no? }
|
|
472
|
+
end
|
|
473
|
+
|
|
435
474
|
# v0.0.2 #6 — diagnostic suppression. Three kinds of
|
|
436
475
|
# suppression compose:
|
|
437
476
|
#
|
|
@@ -516,7 +555,7 @@ module Rigor
|
|
|
516
555
|
line_suppressions = Hash.new { |h, k| h[k] = Set.new }
|
|
517
556
|
file_suppressions = Set.new
|
|
518
557
|
comments.each do |comment|
|
|
519
|
-
source = comment
|
|
558
|
+
source = comment_text(comment)
|
|
520
559
|
if (match = FILE_SUPPRESSION_PATTERN.match(source))
|
|
521
560
|
absorb_suppression_tokens(match[:rules], file_suppressions)
|
|
522
561
|
elsif (match = LINE_SUPPRESSION_PATTERN.match(source))
|
|
@@ -526,6 +565,15 @@ module Rigor
|
|
|
526
565
|
[line_suppressions, file_suppressions]
|
|
527
566
|
end
|
|
528
567
|
|
|
568
|
+
# The comment's text for the suppression regexps. A comment is not validated by Ruby, so it may hold a
|
|
569
|
+
# byte that is invalid in the file's encoding, and a regexp raises on such a string; the marker grammar
|
|
570
|
+
# is ASCII, so scrubbing the bad bytes loses nothing. Positions come from `comment.location`, which is
|
|
571
|
+
# byte-offset based and untouched.
|
|
572
|
+
def comment_text(comment)
|
|
573
|
+
text = comment.location.slice
|
|
574
|
+
text.valid_encoding? ? text : text.scrub("?")
|
|
575
|
+
end
|
|
576
|
+
|
|
529
577
|
def absorb_suppression_tokens(raw, target)
|
|
530
578
|
raw.to_s.split(/[\s,]+/).reject(&:empty?).each do |token|
|
|
531
579
|
target.merge(expand_token(token))
|
|
@@ -543,7 +591,7 @@ module Rigor
|
|
|
543
591
|
# that token is known, so acknowledging it never re-fires the rule.
|
|
544
592
|
def suppression_marker_diagnostics(path, comments)
|
|
545
593
|
comments.each_with_object([]) do |comment, diagnostics|
|
|
546
|
-
source = comment
|
|
594
|
+
source = comment_text(comment)
|
|
547
595
|
if (match = FILE_SUPPRESSION_PATTERN.match(source))
|
|
548
596
|
validate_suppression_tokens(match[:rules], "rigor:disable-file", path, comment, diagnostics)
|
|
549
597
|
elsif (match = LINE_SUPPRESSION_PATTERN.match(source))
|
|
@@ -607,7 +655,7 @@ module Rigor
|
|
|
607
655
|
# `dynamic.*`, `rbs.*`, `pre-eval.*`, and any `plugin.`-prefixed id — plugins load dynamically, so
|
|
608
656
|
# their rule vocabulary cannot be enumerated here and under-warning is the FP-safe direction).
|
|
609
657
|
def known_suppression_token?(token)
|
|
610
|
-
return true if token
|
|
658
|
+
return true if %w[all pre-eval].include?(token)
|
|
611
659
|
return true if ALL_RULES.include?(token) || LEGACY_RULE_ALIASES.key?(token) ||
|
|
612
660
|
RULE_FAMILIES.include?(token) || NON_CHECK_DIAGNOSTIC_IDS.include?(token)
|
|
613
661
|
|
|
@@ -678,13 +726,13 @@ module Rigor
|
|
|
678
726
|
class << self
|
|
679
727
|
private
|
|
680
728
|
|
|
681
|
-
def undefined_method_diagnostic(path, call_node, scope_index, lexical_sites = nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
729
|
+
def undefined_method_diagnostic(path, call_node, scope_index, lexical_sites = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
682
730
|
return nil if call_node.receiver.nil?
|
|
683
731
|
|
|
684
732
|
scope = scope_index[call_node]
|
|
685
733
|
return nil if scope.nil?
|
|
686
734
|
|
|
687
|
-
# The
|
|
735
|
+
# The exemptions that hold whatever shape the receiver has. Ahead of the receiver-shape
|
|
688
736
|
# branch below because a plugin is consulted ONCE for the whole receiver, union included, so the
|
|
689
737
|
# gate belongs where that one consult is rather than duplicated into each shape's path.
|
|
690
738
|
#
|
|
@@ -722,12 +770,18 @@ module Rigor
|
|
|
722
770
|
# the recorded name only: `Merger.nope` still fires.
|
|
723
771
|
return nil if Inference::SingletonObjectConstant.recorded?(call_node, receiver_type, call_node.name, scope)
|
|
724
772
|
|
|
773
|
+
# Issue #1699 — judged as its one class; the message still names the union. See
|
|
774
|
+
# {#single_class_literal_union}.
|
|
775
|
+
rendered_receiver = receiver_type
|
|
776
|
+
receiver_type = single_class_literal_union(receiver_type)
|
|
725
777
|
class_name = concrete_class_name(receiver_type)
|
|
726
778
|
# A union receiver has no single concrete class. The scalar path
|
|
727
779
|
# below cannot reason about it, but the call is still definitely
|
|
728
780
|
# undefined when EVERY arm lacks the method — see
|
|
729
781
|
# `union_undefined_method_diagnostic`.
|
|
730
|
-
|
|
782
|
+
if class_name.nil?
|
|
783
|
+
return union_undefined_method_diagnostic(path, call_node, receiver_type, scope, lexical_sites)
|
|
784
|
+
end
|
|
731
785
|
|
|
732
786
|
# ADR-26 — a plugin may declare a class "open": one
|
|
733
787
|
# known to respond beyond its RBS-declared method
|
|
@@ -782,50 +836,100 @@ module Rigor
|
|
|
782
836
|
# `call.undefined-method` into 171 through exactly this reading, 49 of them here.
|
|
783
837
|
return nil if project_sidecar_owns_method?(scope, class_name, call_node.name, kind)
|
|
784
838
|
|
|
785
|
-
build_undefined_method_diagnostic(path, call_node,
|
|
839
|
+
build_undefined_method_diagnostic(path, call_node, rendered_receiver, definition_site, class_name)
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
# Issue #1717 — an explicit `self.m` / `self.m = v` inside a block whose `self` the engine does not know
|
|
843
|
+
# (`Scope#block_self_unknown?`, set where issue #316's opaque mark is). The receiver still types as the
|
|
844
|
+
# ENCLOSING `self`, but the yielding method may `instance_exec` the block on another object
|
|
845
|
+
# (`ActionController::Renderers.add` runs its block on the controller), so a method missing on the
|
|
846
|
+
# enclosing `self` is no evidence. The implicit `m` in the same block is already silent; the explicit
|
|
847
|
+
# spelling must not be stricter. A block
|
|
848
|
+
# whose `self` IS narrowed (`block_as_methods:`, `define_method`, a `Class.new` body) has cleared the
|
|
849
|
+
# mark and keeps being checked, unless the narrowing was read off an unknown enclosing `self`.
|
|
850
|
+
def unknown_block_self_receiver?(call_node, scope)
|
|
851
|
+
call_node.receiver.is_a?(Prism::SelfNode) && scope.block_self_unknown?
|
|
786
852
|
end
|
|
787
853
|
|
|
788
854
|
# Issue #1120 — the method exists at THIS call site but not everywhere: a refinement whose `using` is in
|
|
789
855
|
# effect here, or a `def o.m` on the receiver local in this scope. Asked from {#last_resort_surface_answers?},
|
|
790
856
|
# once every project-wide table has come back "absent", so the file walk behind {LexicalMethodSites} runs
|
|
791
857
|
# only for a call about to fire.
|
|
792
|
-
# A refinement answers instance-side receivers only: a refined singleton (`refine X.singleton_class`) names
|
|
793
|
-
# no constant target, so nothing records it.
|
|
794
858
|
def lexically_defined_method?(class_name, call_node, scope, kind, lexical_sites)
|
|
795
859
|
return false if lexical_sites.nil?
|
|
796
860
|
return true if lexical_sites.singleton_local_def?(call_node)
|
|
797
|
-
return false unless kind == :instance
|
|
798
861
|
|
|
799
|
-
|
|
800
|
-
# consumer depends on the name whichever way it answers; a refine body edited in another file must
|
|
801
|
-
# re-check it (`IncrementalSession#refinement_affected`).
|
|
802
|
-
DependencyRecorder.read_name(:refinement, call_node.name) if DependencyRecorder.active?
|
|
803
|
-
modules = refining_modules(scope, class_name, call_node.name)
|
|
804
|
-
!modules.nil? && lexical_sites.refinement_active?(call_node, modules)
|
|
862
|
+
refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
805
863
|
end
|
|
806
864
|
|
|
807
|
-
#
|
|
808
|
-
#
|
|
809
|
-
#
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
865
|
+
# Is a refinement of `call_node`'s method name into `class_name` (or an ancestor) in effect at the call?
|
|
866
|
+
# Issue #1120 asks it for a method the class lacks. The two argument rules ask
|
|
867
|
+
# {#refined_redefinition_in_effect?} instead (issue #1740). Typing the call from the refine body is #1664.
|
|
868
|
+
# Issue #1752 — a class-object receiver (`kind` `:singleton`) is reached by a refinement of any class or module
|
|
869
|
+
# on its lookup: `Class`, `Module`, `Object`, `Kernel`, `BasicObject`, or a module it extends. A refined
|
|
870
|
+
# singleton class (`refine X.singleton_class`) names no constant target, so its rows are class-unknown, which
|
|
871
|
+
# reach every receiver. Issue #1673 — the answer is derived from the file's in-effect refinements
|
|
872
|
+
# ({Inference::InEffectRefinements}), the list the typer reads, with each `using` expanded through its
|
|
873
|
+
# module's includes (issue #1671).
|
|
874
|
+
#
|
|
875
|
+
# ADR-121 WD7 — a module in effect whose refinements Rigor cannot read
|
|
876
|
+
# ({Inference::InEffectRefinements.opaque_module?}: a gem's, one from outside the analysed paths, one with an
|
|
877
|
+
# unreadable `refine`) may refine any class, a class object's included, so the call declines whatever its
|
|
878
|
+
# receiver. Issue #1796.
|
|
879
|
+
def refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
880
|
+
return false if lexical_sites.nil?
|
|
881
|
+
return true if opaque_refinement_in_effect?(call_node, scope, lexical_sites)
|
|
882
|
+
|
|
883
|
+
modules = Inference::InEffectRefinements.refining_modules(scope, class_name, call_node.name, kind)
|
|
884
|
+
!modules.nil? &&
|
|
885
|
+
lexical_sites.refinements.refinement_active?(call_node.location.start_offset, modules,
|
|
886
|
+
scope.declared_refinements) do |name|
|
|
887
|
+
Inference::InEffectRefinements.activated_modules(scope, name)
|
|
888
|
+
end
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
# Issues #1663, #1740 — the decline `call.wrong-arity` and `call.argument-type-mismatch` ask once a report
|
|
892
|
+
# would fire: a refinement that REDEFINES a method the class already has replaces the signature those rules
|
|
893
|
+
# check against, but Ruby finds a method the receiver's class (or a nearer ancestor) defines before it
|
|
894
|
+
# consults an ancestor's refinement (`refine Object do def center(a, b, c)` leaves `"x".center(1, 2, 3)` an
|
|
895
|
+
# `ArgumentError`). It stands on its own rather than behind {#refined_method_in_effect?}, whose ancestor
|
|
896
|
+
# reach declines `class Inc; def bar(a); include N; end` under `refine N do def bar(a, b, c)` (issue #1797).
|
|
897
|
+
#
|
|
898
|
+
# A call with nothing in effect is checked. Otherwise it reports only from positive knowledge: the list
|
|
899
|
+
# carries no unknown marker and no opaque module, and {Inference::RefinedDispatch::OwnMethod} proves the
|
|
900
|
+
# receiver's own definer answers first. Issue #1752 — a class-object receiver (`kind` `:singleton`) walks its
|
|
901
|
+
# singleton levels: `Integer.sqrt(1, 2)` under `refine Object do def sqrt(a, b)` runs `Integer.sqrt` and raises.
|
|
902
|
+
def refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
903
|
+
return false if lexical_sites.nil?
|
|
813
904
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
905
|
+
list = refinement_list_at(call_node, scope, lexical_sites)
|
|
906
|
+
return false if list.empty?
|
|
907
|
+
return true if list.include?(Inference::InEffectRefinements::UNKNOWN)
|
|
908
|
+
return true if opaque_refinement_in_effect?(call_node, scope, lexical_sites)
|
|
818
909
|
|
|
819
|
-
|
|
910
|
+
!Inference::RefinedDispatch::OwnMethod.answers?(scope, class_name, call_node.name, list, kind: kind)
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# The in-effect list at the call as the typer reads it: the lexical activations, each `using` expanded,
|
|
914
|
+
# then the plugin-declared modules (#1667).
|
|
915
|
+
def refinement_list_at(call_node, scope, lexical_sites)
|
|
916
|
+
query = lexical_sites.refinements
|
|
917
|
+
declared = scope.declared_refinements
|
|
918
|
+
return Inference::InEffectRefinements::EMPTY if query.empty? && declared.empty?
|
|
919
|
+
|
|
920
|
+
query.at(call_node.location.start_offset, declared) do |name|
|
|
921
|
+
Inference::InEffectRefinements.activated_modules(scope, name)
|
|
820
922
|
end
|
|
821
|
-
modules
|
|
822
923
|
end
|
|
823
924
|
|
|
824
|
-
|
|
825
|
-
|
|
925
|
+
# ADR-121 WD7 — does the call's in-effect list (its lexical activations, each `using` expanded; a
|
|
926
|
+
# plugin-declared module is the plugin's declaration and never opaque) hold an opaque module?
|
|
927
|
+
def opaque_refinement_in_effect?(call_node, scope, lexical_sites)
|
|
928
|
+
query = lexical_sites.refinements
|
|
929
|
+
return false if query.empty?
|
|
826
930
|
|
|
827
|
-
|
|
828
|
-
|
|
931
|
+
list = Inference::InEffectRefinements.lexical_list(scope, query, call_node.location.start_offset)
|
|
932
|
+
Inference::InEffectRefinements.opaque_in?(scope, list)
|
|
829
933
|
end
|
|
830
934
|
|
|
831
935
|
# The probes that run only once every cheaper answer has come back "absent", kept together
|
|
@@ -858,10 +962,25 @@ module Rigor
|
|
|
858
962
|
return true if module_mixin_receiver?(receiver_type, scope)
|
|
859
963
|
return true if mixin_self_class_receiver?(call_node, scope)
|
|
860
964
|
return true if unknown_mixin_includer?(class_name, scope)
|
|
965
|
+
return true if object_mixin_may_define?(receiver_type, class_name, call_node.name, scope)
|
|
861
966
|
|
|
862
967
|
ancestry_declares_method?(scope, class_name, call_node.name, kind)
|
|
863
968
|
end
|
|
864
969
|
|
|
970
|
+
# Issue #1697 — a module the project mixes into `Object` (a top-level `include M`, or `include M` in
|
|
971
|
+
# `class Object`) gives every object its instance methods, and the RBS lookup above only saw core's
|
|
972
|
+
# `Object`. A class object is an `Object` whatever class it is, so the singleton side always reaches
|
|
973
|
+
# it; the instance side does when the receiver's class descends from `Object`. A mixed-in module
|
|
974
|
+
# whose surface is unknown, or a `module_function` reached only privately, does not count here
|
|
975
|
+
# ({Inference::ObjectMixins.may_define?}).
|
|
976
|
+
def object_mixin_may_define?(receiver_type, class_name, method_name, scope)
|
|
977
|
+
unless receiver_type.is_a?(Type::Singleton) || Inference::ObjectMixins.reaches_object?(scope, class_name)
|
|
978
|
+
return false
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
Inference::ObjectMixins.may_define?(scope, method_name, receiver: :explicit)
|
|
982
|
+
end
|
|
983
|
+
|
|
865
984
|
# Issue #739 — the singleton twin of the module-mixin receiver, and the only shape that reaches the
|
|
866
985
|
# singleton side legitimately. `self.class` inside a mixin's instance method is the INCLUDER's class
|
|
867
986
|
# at runtime; the engine types it `Singleton[<the module>]`, one hop along from the same wrong
|
|
@@ -999,6 +1118,15 @@ module Rigor
|
|
|
999
1118
|
# 3. The standard `Kernel` / `Object` private-method
|
|
1000
1119
|
# surface (`puts`, `p`, `require`, `loop`, `raise`,
|
|
1001
1120
|
# …) drawn from the loaded RBS environment.
|
|
1121
|
+
# 4. The private singleton methods of the top-level `main`
|
|
1122
|
+
# object (`using`, `include`, `public`, `private`,
|
|
1123
|
+
# `define_method`), read from RBS core's
|
|
1124
|
+
# `RBS::Unnamed::TopLevelSelfClass`, plus
|
|
1125
|
+
# `ruby2_keywords` (issue #1383).
|
|
1126
|
+
# 5. The instance methods of a module the project mixes
|
|
1127
|
+
# into `Object` — a top-level `include M`, or an
|
|
1128
|
+
# `include M` in `class Object` — or any call at all
|
|
1129
|
+
# when such a module's surface is unknown (issue #1697).
|
|
1002
1130
|
#
|
|
1003
1131
|
# The rule deliberately does NOT generalise to
|
|
1004
1132
|
# implicit-self calls inside `def` / `class` / `module`
|
|
@@ -1010,7 +1138,7 @@ module Rigor
|
|
|
1010
1138
|
# Authored severity is `:warning`; the severity profile
|
|
1011
1139
|
# remaps it (`strict` → `:error`, `balanced` →
|
|
1012
1140
|
# `:warning`, `lenient` → `:off` / suppressed).
|
|
1013
|
-
def unresolved_toplevel_diagnostic(path, call_node, scope_index, eval_ranges = nil)
|
|
1141
|
+
def unresolved_toplevel_diagnostic(path, call_node, scope_index, eval_ranges = nil, lexical_sites = nil)
|
|
1014
1142
|
return nil unless call_node.receiver.nil?
|
|
1015
1143
|
|
|
1016
1144
|
scope = scope_index[call_node]
|
|
@@ -1021,15 +1149,38 @@ module Rigor
|
|
|
1021
1149
|
return nil if scope.top_level_def_for(name)
|
|
1022
1150
|
return nil if source_declared_method?(scope, "Object", name, :instance)
|
|
1023
1151
|
return nil if Rigor::Reflection.instance_method_definition("Object", name, scope: scope)
|
|
1152
|
+
return nil if main_singleton_method?(name, scope)
|
|
1153
|
+
return nil if Inference::ObjectMixins.may_define?(scope, name, receiver: :implicit)
|
|
1024
1154
|
# `Target.class_eval { def added = 1; def use_added = added }` files the defs on Target
|
|
1025
1155
|
# but leaves the eval body with a nil `self_type`, so `toplevel?` still holds. An eval
|
|
1026
1156
|
# body is morally a class body, so ADR-34 stays silent there — including on a genuinely
|
|
1027
1157
|
# undefined name inside the block. Ranges are computed once per file.
|
|
1028
1158
|
return nil if call_inside_receiver_eval_ranges?(eval_ranges, call_node)
|
|
1159
|
+
# ADR-121 WD7 — a refinement of `Object` (or one Rigor cannot read) in effect here may define the name.
|
|
1160
|
+
return nil if refined_method_in_effect?("Object", call_node, scope, :instance, lexical_sites)
|
|
1029
1161
|
|
|
1030
1162
|
build_unresolved_toplevel_diagnostic(path, call_node)
|
|
1031
1163
|
end
|
|
1032
1164
|
|
|
1165
|
+
# Issue #1383 — RBS core declares `main`'s private singleton methods on
|
|
1166
|
+
# `RBS::Unnamed::TopLevelSelfClass`, so the set follows RBS rather than a hand-kept
|
|
1167
|
+
# list. CRuby 4.0's `main` also has `ruby2_keywords`, which RBS does not declare.
|
|
1168
|
+
# rbs 3.x has no `TopLevelSelfClass`, so there only `ruby2_keywords` is covered.
|
|
1169
|
+
# A call inside a top-level block (`describe do include M end`) is judged here too;
|
|
1170
|
+
# silencing it is the cheaper error.
|
|
1171
|
+
MAIN_SINGLETON_CLASS_NAME = "RBS::Unnamed::TopLevelSelfClass"
|
|
1172
|
+
MAIN_SINGLETON_METHODS_MISSING_FROM_RBS = %i[ruby2_keywords].freeze
|
|
1173
|
+
private_constant :MAIN_SINGLETON_CLASS_NAME, :MAIN_SINGLETON_METHODS_MISSING_FROM_RBS
|
|
1174
|
+
|
|
1175
|
+
def main_singleton_method?(name, scope)
|
|
1176
|
+
return true if MAIN_SINGLETON_METHODS_MISSING_FROM_RBS.include?(name)
|
|
1177
|
+
# `TopLevelSelfClass < Object`: when the project's own RBS broke `Object`, building it would only
|
|
1178
|
+
# fail again and report a second, unfixable class name beside `Object` (#696's report).
|
|
1179
|
+
return false if Rigor::Reflection.instance_definition("Object", scope: scope).nil?
|
|
1180
|
+
|
|
1181
|
+
!Rigor::Reflection.instance_method_definition(MAIN_SINGLETON_CLASS_NAME, name, scope: scope).nil?
|
|
1182
|
+
end
|
|
1183
|
+
|
|
1033
1184
|
# ScopeIndexer keeps RECEIVER_EVAL_CALLS private; this list is the same family
|
|
1034
1185
|
# (`class_eval` / `module_eval` / `class_exec` / `module_exec` / `instance_eval` /
|
|
1035
1186
|
# `instance_exec`). Used only to decline ADR-34 inside those blocks.
|
|
@@ -1305,8 +1456,11 @@ module Rigor
|
|
|
1305
1456
|
# - ADR-67 WD6b — an inferred-parameter receiver's type is an open-call-site lower bound, so firing
|
|
1306
1457
|
# undefined-method against it is a false positive by construction.
|
|
1307
1458
|
# - Issue #653 — a plugin answered this call site ({#plugin_typed_call?}).
|
|
1459
|
+
# - Issue #1717 — an explicit `self` receiver in a block whose `self` is unmodelled
|
|
1460
|
+
# ({#unknown_block_self_receiver?}).
|
|
1308
1461
|
def call_site_exempt?(call_node, scope)
|
|
1309
|
-
inferred_param_receiver?(call_node, scope) || plugin_typed_call?(call_node, scope)
|
|
1462
|
+
inferred_param_receiver?(call_node, scope) || plugin_typed_call?(call_node, scope) ||
|
|
1463
|
+
unknown_block_self_receiver?(call_node, scope)
|
|
1310
1464
|
end
|
|
1311
1465
|
|
|
1312
1466
|
# Issue #653 — true when a plugin's `dynamic_return` supplied the return type for THIS call node
|
|
@@ -1568,7 +1722,7 @@ module Rigor
|
|
|
1568
1722
|
# when the call's receiver / RBS coverage / call shape
|
|
1569
1723
|
# disqualifies the rule.
|
|
1570
1724
|
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1571
|
-
def wrong_arity_diagnostic(path, call_node, scope_index)
|
|
1725
|
+
def wrong_arity_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
1572
1726
|
return nil if call_node.receiver.nil?
|
|
1573
1727
|
return nil unless plain_positional_call?(call_node)
|
|
1574
1728
|
|
|
@@ -1609,11 +1763,18 @@ module Rigor
|
|
|
1609
1763
|
source_arity&.settle_by_definitions
|
|
1610
1764
|
return nil
|
|
1611
1765
|
end
|
|
1766
|
+
return nil if arity_report_withheld?(source_arity, class_name, call_node, scope, kind, lexical_sites)
|
|
1767
|
+
|
|
1768
|
+
build_arity_diagnostic(path, call_node, class_name, min, max, actual)
|
|
1769
|
+
end
|
|
1770
|
+
|
|
1771
|
+
# The declines `call.wrong-arity` asks only once the arity is out of the envelope.
|
|
1772
|
+
def arity_report_withheld?(source_arity, class_name, call_node, scope, kind, lexical_sites)
|
|
1612
1773
|
# Issue #992 — a source envelope's remaining declines can only withhold, so they run only now.
|
|
1613
1774
|
source_arity&.settle_by_walk
|
|
1614
|
-
return
|
|
1775
|
+
return true if source_arity && !source_arity.authoritative?(class_name)
|
|
1615
1776
|
|
|
1616
|
-
|
|
1777
|
+
refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
1617
1778
|
end
|
|
1618
1779
|
|
|
1619
1780
|
# The `[min, max]` the call is checked against — a declared signature's, or (issue #992) the project
|
|
@@ -1813,7 +1974,7 @@ module Rigor
|
|
|
1813
1974
|
# and union receivers where every member already
|
|
1814
1975
|
# disqualifies the call (avoid duplicating the
|
|
1815
1976
|
# undefined-method diagnostic).
|
|
1816
|
-
def nil_receiver_diagnostic(path, call_node, scope_index)
|
|
1977
|
+
def nil_receiver_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
1817
1978
|
return nil if call_node.receiver.nil?
|
|
1818
1979
|
# Safe-navigation calls (`recv&.method`) already
|
|
1819
1980
|
# short-circuit on nil at runtime, so a nil-bearing
|
|
@@ -1862,10 +2023,22 @@ module Rigor
|
|
|
1862
2023
|
return nil unless Rigor::Reflection.rbs_class_known?("NilClass", scope: scope)
|
|
1863
2024
|
|
|
1864
2025
|
return nil unless nil_bearing_union_witnesses?(receiver_type, call_node.name, scope)
|
|
2026
|
+
return nil if nil_receiver_withheld?(call_node, receiver_type, scope, scope_index, lexical_sites)
|
|
1865
2027
|
|
|
1866
2028
|
build_nil_receiver_diagnostic(path, call_node)
|
|
1867
2029
|
end
|
|
1868
2030
|
|
|
2031
|
+
# The declines `call.possible-nil-receiver` asks only once a report would fire.
|
|
2032
|
+
def nil_receiver_withheld?(call_node, receiver_type, scope, scope_index, lexical_sites)
|
|
2033
|
+
# Issue #1703 — a `nil` that only a `key?` guard on the same receiver and key rules out. It re-walks the file
|
|
2034
|
+
# once with guards on, and only to withhold this report.
|
|
2035
|
+
return true if Inference::KeyPresenceGuard.withholds_nil?(call_node, receiver_type, lexical_sites&.root,
|
|
2036
|
+
scope_index)
|
|
2037
|
+
|
|
2038
|
+
# ADR-121 WD7 — a refinement of `NilClass` (or one Rigor cannot read) in effect may define the name.
|
|
2039
|
+
refined_method_in_effect?("NilClass", call_node, scope, :instance, lexical_sites)
|
|
2040
|
+
end
|
|
2041
|
+
|
|
1869
2042
|
# The receiver-type half of the rule, factored out of the node-shape
|
|
1870
2043
|
# guards above: the union must carry nil, must carry a non-nil arm the
|
|
1871
2044
|
# presence question can be asked of, must support the method on every
|
|
@@ -1914,7 +2087,13 @@ module Rigor
|
|
|
1914
2087
|
# arm restores the polarity without touching the shared helper:
|
|
1915
2088
|
# `String | nil` keeps firing, and so does `Dynamic | String | nil` —
|
|
1916
2089
|
# the nameless arm stays permissive inside the all-arms check, which is
|
|
1917
|
-
# only about the arms' method surface.
|
|
2090
|
+
# only about the arms' method surface. That union no longer arises from
|
|
2091
|
+
# a merge re-joining the narrowed edges of one untyped local, which
|
|
2092
|
+
# binds it back to `Dynamic[top]` (issue #1840,
|
|
2093
|
+
# `Inference::UntypedRejoin`); it still does when the program writes
|
|
2094
|
+
# the `nil` (`x = nil if c`) and no `nil?` edge strips it before the
|
|
2095
|
+
# re-joining merge, or when the arms are not a narrowing of one
|
|
2096
|
+
# untyped value.
|
|
1918
2097
|
def union_has_nameable_non_nil_arm?(union, scope)
|
|
1919
2098
|
union.members.any? do |m|
|
|
1920
2099
|
next false if nil_member?(m)
|
|
@@ -1957,7 +2136,9 @@ module Rigor
|
|
|
1957
2136
|
|
|
1958
2137
|
# Issue #723 — the arm's own RBS does not have it; its project ancestry still might. Last, for
|
|
1959
2138
|
# the reason {#ancestry_declares_method?} carries: this is the probe that walks the class graph.
|
|
1960
|
-
ancestry_declares_method?(scope, class_name, method_name, :instance)
|
|
2139
|
+
return true if ancestry_declares_method?(scope, class_name, method_name, :instance)
|
|
2140
|
+
|
|
2141
|
+
object_mixin_may_define?(member, class_name, method_name, scope)
|
|
1961
2142
|
end
|
|
1962
2143
|
|
|
1963
2144
|
def nil_class_has_method?(method_name, scope)
|
|
@@ -1984,7 +2165,7 @@ module Rigor
|
|
|
1984
2165
|
# `possible-nil-receiver` rule, safe-navigation, and ADR-58
|
|
1985
2166
|
# declaration-sourced nil. Slice 1 handles pure non-nil unions
|
|
1986
2167
|
# (e.g. `String | Symbol`).
|
|
1987
|
-
def union_undefined_method_diagnostic(path, call_node, receiver_type, scope)
|
|
2168
|
+
def union_undefined_method_diagnostic(path, call_node, receiver_type, scope, lexical_sites = nil)
|
|
1988
2169
|
return nil unless receiver_type.is_a?(Type::Union)
|
|
1989
2170
|
return nil if call_node.safe_navigation?
|
|
1990
2171
|
|
|
@@ -2006,10 +2187,45 @@ module Rigor
|
|
|
2006
2187
|
# `.pack`). Require at least two distinct arm classes.
|
|
2007
2188
|
return nil if members.map { |member| concrete_class_name(member) }.uniq.size < 2
|
|
2008
2189
|
return nil if members.any? { |member| method_present_anywhere?(member, call_node.name, scope) }
|
|
2190
|
+
return nil if refined_on_some_arm?(members, call_node, scope, lexical_sites)
|
|
2009
2191
|
|
|
2010
2192
|
build_undefined_method_diagnostic(path, call_node, receiver_type)
|
|
2011
2193
|
end
|
|
2012
2194
|
|
|
2195
|
+
# ADR-121 WD7 — a refinement in effect (or one Rigor cannot read) may define the name on one arm's class.
|
|
2196
|
+
def refined_on_some_arm?(members, call_node, scope, lexical_sites)
|
|
2197
|
+
members.any? do |member|
|
|
2198
|
+
kind = member.is_a?(Type::Singleton) ? :singleton : :instance
|
|
2199
|
+
refined_method_in_effect?(concrete_class_name(member), call_node, scope, kind, lexical_sites)
|
|
2200
|
+
end
|
|
2201
|
+
end
|
|
2202
|
+
|
|
2203
|
+
# Issue #1699 — a union of one class's literals, alone or beside that class's plain nominal
|
|
2204
|
+
# (`0 | Integer`, which `gets.to_i` types; `"a" | "b"`; `1 | 2`), holds nothing but instances of
|
|
2205
|
+
# that class, so it answers a method-existence question exactly as the class does. The union rule
|
|
2206
|
+
# above declines it through its distinct-class guard, which exists for SHAPE joins
|
|
2207
|
+
# (`Hash[K1, V1] | Hash[K2, V2]`, a corpus misinference on mail), and the scalar rule declined it
|
|
2208
|
+
# for having no single concrete class, so neither judged it. Returns that class's nominal, or `type`
|
|
2209
|
+
# itself. Literal-and-plain-nominal arms only: a generic, shape or refinement arm keeps the union
|
|
2210
|
+
# declined, and so does a union spanning two classes.
|
|
2211
|
+
def single_class_literal_union(type)
|
|
2212
|
+
return type unless type.is_a?(Type::Union)
|
|
2213
|
+
|
|
2214
|
+
members = type.members
|
|
2215
|
+
return type unless members.any?(Type::Constant)
|
|
2216
|
+
return type unless members.all? do |member|
|
|
2217
|
+
member.is_a?(Type::Constant) || (member.is_a?(Type::Nominal) && member.type_args.empty?)
|
|
2218
|
+
end
|
|
2219
|
+
|
|
2220
|
+
class_names = members.map { |member| concrete_class_name(member) }.uniq
|
|
2221
|
+
return type unless class_names.size == 1
|
|
2222
|
+
|
|
2223
|
+
class_name = class_names.first
|
|
2224
|
+
return type if class_name.nil? || class_name == "NilClass"
|
|
2225
|
+
|
|
2226
|
+
Type::Combinator.nominal_of(class_name)
|
|
2227
|
+
end
|
|
2228
|
+
|
|
2013
2229
|
# An arm that makes a sound "undefined on every arm" verdict impossible: a non-class surface
|
|
2014
2230
|
# (Dynamic / Top / Bot), a singleton (slice 1 reasons about instance arms only), the generic
|
|
2015
2231
|
# metaclass `Class` / `Module` (a value typed as one is *some* class/module object whose singleton
|
|
@@ -2150,9 +2366,13 @@ module Rigor
|
|
|
2150
2366
|
INTEGER_RAISING_OPERATORS = %i[/ % div modulo divmod].freeze
|
|
2151
2367
|
private_constant :INTEGER_RAISING_OPERATORS
|
|
2152
2368
|
|
|
2153
|
-
def always_raises_diagnostic(path, call_node, scope_index)
|
|
2369
|
+
def always_raises_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
2154
2370
|
return nil unless integer_zero_division?(call_node, scope_index)
|
|
2155
2371
|
|
|
2372
|
+
# ADR-121 WD7 — a refinement of `Integer`'s operator (or one Rigor cannot read) in effect may not raise.
|
|
2373
|
+
scope = scope_index[call_node]
|
|
2374
|
+
return nil if refined_method_in_effect?("Integer", call_node, scope, :instance, lexical_sites)
|
|
2375
|
+
|
|
2156
2376
|
build_always_raises_diagnostic(path, call_node)
|
|
2157
2377
|
end
|
|
2158
2378
|
|
|
@@ -2493,7 +2713,7 @@ module Rigor
|
|
|
2493
2713
|
names = [method_name, *GLOBAL_WRITE_HATCHES]
|
|
2494
2714
|
census = global_write_census(scope)
|
|
2495
2715
|
program_may_answer?(ancestors, names, census, scope) ||
|
|
2496
|
-
(method_name == :write && refined_writer_in_effect?(census, node, lexical_sites))
|
|
2716
|
+
(method_name == :write && refined_writer_in_effect?(census, node, scope, lexical_sites))
|
|
2497
2717
|
end
|
|
2498
2718
|
|
|
2499
2719
|
def overridden_hatch?(definition)
|
|
@@ -2549,9 +2769,10 @@ module Rigor
|
|
|
2549
2769
|
# refine(T)`), so a refined `write` on any class declines every literal. `respond_to?(:write)` honours an
|
|
2550
2770
|
# active refinement on Ruby 4.0.5; a refined `respond_to_missing?` or `respond_to?`, and every implicit
|
|
2551
2771
|
# conversion, do not, so a refinement that adds only those leaves the write reported.
|
|
2552
|
-
def refined_writer_in_effect?(census, node, lexical_sites)
|
|
2772
|
+
def refined_writer_in_effect?(census, node, scope, lexical_sites)
|
|
2553
2773
|
Inference::GlobalWriteCensus.refines_write?(census) &&
|
|
2554
|
-
(lexical_sites.nil? ||
|
|
2774
|
+
(lexical_sites.nil? || !scope.declared_refinements.empty? ||
|
|
2775
|
+
lexical_sites.refinements.any_at?(node.location.start_offset))
|
|
2555
2776
|
end
|
|
2556
2777
|
|
|
2557
2778
|
def build_global_write_type_diagnostic(path, node, contract, class_name)
|
|
@@ -2634,7 +2855,7 @@ module Rigor
|
|
|
2634
2855
|
# - Issue #1568 — a module the class PREPENDS sits ahead of it in Ruby's order, so a definition there
|
|
2635
2856
|
# is what the call reaches; the class's own private `def` answers only when nothing prepended
|
|
2636
2857
|
# defines the name ({#reached_definer_visibility}).
|
|
2637
|
-
def visibility_mismatch_diagnostic(path, call_node, scope_index)
|
|
2858
|
+
def visibility_mismatch_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
2638
2859
|
return nil unless explicit_non_self_receiver?(call_node.receiver)
|
|
2639
2860
|
|
|
2640
2861
|
scope = scope_index[call_node]
|
|
@@ -2653,6 +2874,8 @@ module Rigor
|
|
|
2653
2874
|
reached = reached_definer_visibility(scope, receiver_type.class_name, call_node.name)
|
|
2654
2875
|
return nil if reached == :undecided
|
|
2655
2876
|
return nil unless reached.nil? || reached == :private
|
|
2877
|
+
# ADR-121 WD7 — a refinement in effect (or one Rigor cannot read) may redefine the method, public.
|
|
2878
|
+
return nil if refined_method_in_effect?(receiver_type.class_name, call_node, scope, :instance, lexical_sites)
|
|
2656
2879
|
|
|
2657
2880
|
build_visibility_mismatch_diagnostic(path, call_node, receiver_type)
|
|
2658
2881
|
end
|
|
@@ -2905,7 +3128,7 @@ module Rigor
|
|
|
2905
3128
|
COERCE_DISPATCH_METHODS = %i[+ - * / % ** & | ^ << >> < > <= >=].to_set.freeze
|
|
2906
3129
|
private_constant :COERCE_DISPATCH_METHODS
|
|
2907
3130
|
|
|
2908
|
-
def argument_type_diagnostic(path, call_node, scope_index)
|
|
3131
|
+
def argument_type_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
2909
3132
|
return nil if call_node.receiver.nil?
|
|
2910
3133
|
return nil if UNIVERSAL_EQUALITY_METHODS.include?(call_node.name)
|
|
2911
3134
|
return nil unless plain_positional_call?(call_node)
|
|
@@ -2932,11 +3155,19 @@ module Rigor
|
|
|
2932
3155
|
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def, environment: scope.environment)
|
|
2933
3156
|
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, scope_index)
|
|
2934
3157
|
return nil if mismatch.nil?
|
|
2935
|
-
return nil if
|
|
3158
|
+
return nil if argument_report_withheld?(receiver_type, class_name, call_node, mismatch, scope, lexical_sites)
|
|
2936
3159
|
|
|
2937
3160
|
build_argument_type_diagnostic(path, call_node, class_name, mismatch)
|
|
2938
3161
|
end
|
|
2939
3162
|
|
|
3163
|
+
# The declines `call.argument-type-mismatch` asks only once an argument mismatches.
|
|
3164
|
+
def argument_report_withheld?(receiver_type, class_name, call_node, mismatch, scope, lexical_sites)
|
|
3165
|
+
return true if inferred_param_mismatch_verdict?(call_node, mismatch, scope)
|
|
3166
|
+
|
|
3167
|
+
kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
|
|
3168
|
+
refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
3169
|
+
end
|
|
3170
|
+
|
|
2940
3171
|
# ADR-67 WD6b — an argument-type-mismatch verdict resting on an open-call-site lower bound, on
|
|
2941
3172
|
# either side of the call. The ARGUMENT side: the mismatching argument is an inferred-parameter
|
|
2942
3173
|
# local, so firing against it is an FP by construction. The RECEIVER side: when the receiver roots
|
|
@@ -3738,6 +3969,7 @@ module Rigor
|
|
|
3738
3969
|
# silent.
|
|
3739
3970
|
def override_visibility_diagnostic(path, def_node, scope_index)
|
|
3740
3971
|
return nil unless def_node.receiver.nil? # instance methods only
|
|
3972
|
+
return nil if IMPLICITLY_PRIVATE_METHODS.include?(def_node.name)
|
|
3741
3973
|
|
|
3742
3974
|
scope = scope_index[def_node]
|
|
3743
3975
|
return nil if scope.nil?
|
|
@@ -3869,6 +4101,7 @@ module Rigor
|
|
|
3869
4101
|
# fire) when any gate is unmet.
|
|
3870
4102
|
def resolve_authored_override(def_node, scope_index)
|
|
3871
4103
|
return nil unless def_node.receiver.nil? # instance methods only (singleton: follow-on)
|
|
4104
|
+
return nil if LIFECYCLE_HOOKS.include?(def_node.name) # ADR-35 WD10: outside substitutability
|
|
3872
4105
|
|
|
3873
4106
|
scope = scope_index[def_node]
|
|
3874
4107
|
return nil if scope.nil?
|