rigortype 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/03-configuration.md +1 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +289 -57
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +3 -1
- data/lib/rigor/analysis/incremental_session.rb +14 -1
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
- data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +34 -3
- data/lib/rigor/analysis/worker_session.rb +7 -4
- data/lib/rigor/cache/incremental_snapshot.rb +22 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +13 -7
- data/lib/rigor/cli.rb +10 -2
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +46 -2
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +283 -14
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment.rb +21 -3
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +215 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +259 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +687 -60
- data/lib/rigor/inference/statement_evaluator.rb +71 -20
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/mutation_cache.rb +1 -1
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +189 -10
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +55 -5
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +27 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- metadata +66 -1
|
@@ -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.
|
|
@@ -96,8 +109,11 @@ module Rigor
|
|
|
96
109
|
# direct caller with no plugin walk, e.g. a unit test), the standalone
|
|
97
110
|
# {RuleWalk} walk runs here instead, so `diagnose` stays correct
|
|
98
111
|
# without the converged path.
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
#
|
|
113
|
+
# @param stated_runtime_ruby — {Configuration#stated_runtime_ruby}: the explicit `target_ruby`, or nil. Only
|
|
114
|
+
# {RubyDeprecations} reads it, and it walks the file only when the stated Ruby deprecates something.
|
|
115
|
+
def diagnose(path:, root:, scope_index:, self_call_misses: [], comments: [], disabled_rules: [], # rubocop:disable Metrics/ParameterLists
|
|
116
|
+
node_collectors: nil, stated_runtime_ruby: nil)
|
|
101
117
|
collectors = node_collectors || run_node_collectors(path, root, scope_index)
|
|
102
118
|
diagnostics = collectors[:main_pass].results.dup
|
|
103
119
|
diagnostics.concat(self_undefined_method_diagnostics(path, self_call_misses, root, scope_index))
|
|
@@ -109,6 +125,9 @@ module Rigor
|
|
|
109
125
|
# `suppression.*` joins the list, which stays reportable inside a dead arm — a malformed
|
|
110
126
|
# `# rigor:disable` marker is an authoring error regardless of whether its code runs.
|
|
111
127
|
diagnostics = DeadVersionGuardArms.filter(diagnostics, root)
|
|
128
|
+
# The ADR-47 WD5 amendment (#1692) — joined after that filter: the deprecation rule reads each version guard
|
|
129
|
+
# against the stated Ruby itself, and the analyzer's own Ruby says nothing about where the call runs.
|
|
130
|
+
diagnostics.concat(RubyDeprecations.diagnostics(path, root, scope_index, stated_runtime_ruby))
|
|
112
131
|
# Suppression-marker validation (`suppression.*`) runs BEFORE the filter so its own diagnostics are
|
|
113
132
|
# suppressible like any other rule — `# rigor:disable suppression.unknown-rule` on the offending
|
|
114
133
|
# comment's line works, with no regress (the token itself is known, so it never re-fires).
|
|
@@ -166,7 +185,7 @@ module Rigor
|
|
|
166
185
|
diagnostic = return_type_mismatch_diagnostic(path, node, scope_index)
|
|
167
186
|
return diagnostic if diagnostic.nil? || lexical_sites.nil?
|
|
168
187
|
|
|
169
|
-
lexical_sites.refinement_def?(node) ? nil : diagnostic
|
|
188
|
+
lexical_sites.refinements.refinement_def?(node) ? nil : diagnostic
|
|
170
189
|
end
|
|
171
190
|
|
|
172
191
|
# Constructs the fresh, unpopulated built-in collector set keyed by
|
|
@@ -178,7 +197,7 @@ module Rigor
|
|
|
178
197
|
# diagnostics carry it (ADR-53 B3c hosts it on the same walk).
|
|
179
198
|
def build_node_collectors(path, scope_index, root = nil)
|
|
180
199
|
eval_ranges = receiver_eval_block_ranges(root)
|
|
181
|
-
lexical_sites = LexicalMethodSites.new(root)
|
|
200
|
+
lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
|
|
182
201
|
main_pass = ->(node) { main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites) }
|
|
183
202
|
{
|
|
184
203
|
main_pass: MainPassCollector.new(main_pass),
|
|
@@ -252,6 +271,14 @@ module Rigor
|
|
|
252
271
|
collector.class.new(scope_index).collect(root)
|
|
253
272
|
end
|
|
254
273
|
|
|
274
|
+
# Issue #1673 — the in-effect refinements `Inference::ScopeIndexer` stamped for `root`, read off the root's scope
|
|
275
|
+
# so the rules answer from the query the typer reads; nil where the index holds no scope for it.
|
|
276
|
+
def stamped_refinements(scope_index, root)
|
|
277
|
+
return nil if root.nil? || scope_index.nil?
|
|
278
|
+
|
|
279
|
+
scope_index[root]&.discovery&.in_effect_refinements
|
|
280
|
+
end
|
|
281
|
+
|
|
255
282
|
# The former inline main pass, kept as the shadow oracle: walks the
|
|
256
283
|
# tree with `Source::NodeWalker.each` and accumulates the same
|
|
257
284
|
# per-node diagnostics in the same order {MainPassCollector} now
|
|
@@ -259,7 +286,7 @@ module Rigor
|
|
|
259
286
|
def main_pass_oracle(path, root, scope_index)
|
|
260
287
|
diagnostics = []
|
|
261
288
|
eval_ranges = receiver_eval_block_ranges(root)
|
|
262
|
-
lexical_sites = LexicalMethodSites.new(root)
|
|
289
|
+
lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
|
|
263
290
|
Source::NodeWalker.each(root) do |node|
|
|
264
291
|
diagnostics.concat(main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites))
|
|
265
292
|
end
|
|
@@ -284,15 +311,15 @@ module Rigor
|
|
|
284
311
|
def call_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil)
|
|
285
312
|
[
|
|
286
313
|
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),
|
|
314
|
+
unresolved_toplevel_diagnostic(path, node, scope_index, eval_ranges, lexical_sites),
|
|
315
|
+
wrong_arity_diagnostic(path, node, scope_index, lexical_sites),
|
|
316
|
+
argument_type_diagnostic(path, node, scope_index, lexical_sites),
|
|
317
|
+
nil_receiver_diagnostic(path, node, scope_index, lexical_sites),
|
|
291
318
|
dump_type_diagnostic(path, node, scope_index),
|
|
292
319
|
assert_type_diagnostic(path, node, scope_index),
|
|
293
|
-
always_raises_diagnostic(path, node, scope_index),
|
|
320
|
+
always_raises_diagnostic(path, node, scope_index, lexical_sites),
|
|
294
321
|
raise_non_exception_diagnostic(path, node, scope_index),
|
|
295
|
-
visibility_mismatch_diagnostic(path, node, scope_index)
|
|
322
|
+
visibility_mismatch_diagnostic(path, node, scope_index, lexical_sites)
|
|
296
323
|
].compact
|
|
297
324
|
end
|
|
298
325
|
|
|
@@ -316,6 +343,10 @@ module Rigor
|
|
|
316
343
|
# Class-level ivars (`@x = 1` outside any def, in the
|
|
317
344
|
# class body) are also skipped — they're a separate
|
|
318
345
|
# surface (`Module#@var`) the engine doesn't yet model.
|
|
346
|
+
# - A divergence the class's RBS declaration admits is withheld: when the ivar is declared (`@x: T`,
|
|
347
|
+
# or the slot an `attr_*` member implies) and that type accepts both the first write and the
|
|
348
|
+
# later one, the union is the author's intent. The declaration only silences here; checking
|
|
349
|
+
# writes against it is #1406.
|
|
319
350
|
def ivar_write_mismatch_diagnostics(path, ivar_writes)
|
|
320
351
|
ivar_writes.flat_map do |class_name, writes_by_ivar|
|
|
321
352
|
writes_by_ivar.flat_map do |ivar_name, writes|
|
|
@@ -427,11 +458,18 @@ module Rigor
|
|
|
427
458
|
writes[(canonical_index + 1)..].filter_map do |write|
|
|
428
459
|
other_class = ivar_class_for(write[:type])
|
|
429
460
|
next nil if other_class.nil? || other_class == "NilClass" || other_class == first_class
|
|
461
|
+
next nil if declaration_admits?(write[:declared], canonical[:type], write[:type])
|
|
430
462
|
|
|
431
463
|
build_ivar_write_mismatch_diagnostic(path, write[:node], class_name, ivar_name, first_class, other_class)
|
|
432
464
|
end
|
|
433
465
|
end
|
|
434
466
|
|
|
467
|
+
def declaration_admits?(declared, *write_types)
|
|
468
|
+
return false if declared.nil?
|
|
469
|
+
|
|
470
|
+
write_types.none? { |type| Inference::Acceptance.accepts(declared, type, mode: :gradual).no? }
|
|
471
|
+
end
|
|
472
|
+
|
|
435
473
|
# v0.0.2 #6 — diagnostic suppression. Three kinds of
|
|
436
474
|
# suppression compose:
|
|
437
475
|
#
|
|
@@ -516,7 +554,7 @@ module Rigor
|
|
|
516
554
|
line_suppressions = Hash.new { |h, k| h[k] = Set.new }
|
|
517
555
|
file_suppressions = Set.new
|
|
518
556
|
comments.each do |comment|
|
|
519
|
-
source = comment
|
|
557
|
+
source = comment_text(comment)
|
|
520
558
|
if (match = FILE_SUPPRESSION_PATTERN.match(source))
|
|
521
559
|
absorb_suppression_tokens(match[:rules], file_suppressions)
|
|
522
560
|
elsif (match = LINE_SUPPRESSION_PATTERN.match(source))
|
|
@@ -526,6 +564,15 @@ module Rigor
|
|
|
526
564
|
[line_suppressions, file_suppressions]
|
|
527
565
|
end
|
|
528
566
|
|
|
567
|
+
# The comment's text for the suppression regexps. A comment is not validated by Ruby, so it may hold a
|
|
568
|
+
# byte that is invalid in the file's encoding, and a regexp raises on such a string; the marker grammar
|
|
569
|
+
# is ASCII, so scrubbing the bad bytes loses nothing. Positions come from `comment.location`, which is
|
|
570
|
+
# byte-offset based and untouched.
|
|
571
|
+
def comment_text(comment)
|
|
572
|
+
text = comment.location.slice
|
|
573
|
+
text.valid_encoding? ? text : text.scrub("?")
|
|
574
|
+
end
|
|
575
|
+
|
|
529
576
|
def absorb_suppression_tokens(raw, target)
|
|
530
577
|
raw.to_s.split(/[\s,]+/).reject(&:empty?).each do |token|
|
|
531
578
|
target.merge(expand_token(token))
|
|
@@ -543,7 +590,7 @@ module Rigor
|
|
|
543
590
|
# that token is known, so acknowledging it never re-fires the rule.
|
|
544
591
|
def suppression_marker_diagnostics(path, comments)
|
|
545
592
|
comments.each_with_object([]) do |comment, diagnostics|
|
|
546
|
-
source = comment
|
|
593
|
+
source = comment_text(comment)
|
|
547
594
|
if (match = FILE_SUPPRESSION_PATTERN.match(source))
|
|
548
595
|
validate_suppression_tokens(match[:rules], "rigor:disable-file", path, comment, diagnostics)
|
|
549
596
|
elsif (match = LINE_SUPPRESSION_PATTERN.match(source))
|
|
@@ -678,13 +725,13 @@ module Rigor
|
|
|
678
725
|
class << self
|
|
679
726
|
private
|
|
680
727
|
|
|
681
|
-
def undefined_method_diagnostic(path, call_node, scope_index, lexical_sites = nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
728
|
+
def undefined_method_diagnostic(path, call_node, scope_index, lexical_sites = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
682
729
|
return nil if call_node.receiver.nil?
|
|
683
730
|
|
|
684
731
|
scope = scope_index[call_node]
|
|
685
732
|
return nil if scope.nil?
|
|
686
733
|
|
|
687
|
-
# The
|
|
734
|
+
# The exemptions that hold whatever shape the receiver has. Ahead of the receiver-shape
|
|
688
735
|
# branch below because a plugin is consulted ONCE for the whole receiver, union included, so the
|
|
689
736
|
# gate belongs where that one consult is rather than duplicated into each shape's path.
|
|
690
737
|
#
|
|
@@ -722,12 +769,18 @@ module Rigor
|
|
|
722
769
|
# the recorded name only: `Merger.nope` still fires.
|
|
723
770
|
return nil if Inference::SingletonObjectConstant.recorded?(call_node, receiver_type, call_node.name, scope)
|
|
724
771
|
|
|
772
|
+
# Issue #1699 — judged as its one class; the message still names the union. See
|
|
773
|
+
# {#single_class_literal_union}.
|
|
774
|
+
rendered_receiver = receiver_type
|
|
775
|
+
receiver_type = single_class_literal_union(receiver_type)
|
|
725
776
|
class_name = concrete_class_name(receiver_type)
|
|
726
777
|
# A union receiver has no single concrete class. The scalar path
|
|
727
778
|
# below cannot reason about it, but the call is still definitely
|
|
728
779
|
# undefined when EVERY arm lacks the method — see
|
|
729
780
|
# `union_undefined_method_diagnostic`.
|
|
730
|
-
|
|
781
|
+
if class_name.nil?
|
|
782
|
+
return union_undefined_method_diagnostic(path, call_node, receiver_type, scope, lexical_sites)
|
|
783
|
+
end
|
|
731
784
|
|
|
732
785
|
# ADR-26 — a plugin may declare a class "open": one
|
|
733
786
|
# known to respond beyond its RBS-declared method
|
|
@@ -782,50 +835,107 @@ module Rigor
|
|
|
782
835
|
# `call.undefined-method` into 171 through exactly this reading, 49 of them here.
|
|
783
836
|
return nil if project_sidecar_owns_method?(scope, class_name, call_node.name, kind)
|
|
784
837
|
|
|
785
|
-
build_undefined_method_diagnostic(path, call_node,
|
|
838
|
+
build_undefined_method_diagnostic(path, call_node, rendered_receiver, definition_site, class_name)
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
# Issue #1717 — an explicit `self.m` / `self.m = v` inside a block whose `self` the engine does not know
|
|
842
|
+
# (`Scope#block_self_unknown?`, set where issue #316's opaque mark is). The receiver still types as the
|
|
843
|
+
# ENCLOSING `self`, but the yielding method may `instance_exec` the block on another object
|
|
844
|
+
# (`ActionController::Renderers.add` runs its block on the controller), so a method missing on the
|
|
845
|
+
# enclosing `self` is no evidence. The implicit `m` in the same block is already silent; the explicit
|
|
846
|
+
# spelling must not be stricter. A block
|
|
847
|
+
# whose `self` IS narrowed (`block_as_methods:`, `define_method`, a `Class.new` body) has cleared the
|
|
848
|
+
# mark and keeps being checked, unless the narrowing was read off an unknown enclosing `self`.
|
|
849
|
+
def unknown_block_self_receiver?(call_node, scope)
|
|
850
|
+
call_node.receiver.is_a?(Prism::SelfNode) && scope.block_self_unknown?
|
|
786
851
|
end
|
|
787
852
|
|
|
788
853
|
# Issue #1120 — the method exists at THIS call site but not everywhere: a refinement whose `using` is in
|
|
789
854
|
# effect here, or a `def o.m` on the receiver local in this scope. Asked from {#last_resort_surface_answers?},
|
|
790
855
|
# once every project-wide table has come back "absent", so the file walk behind {LexicalMethodSites} runs
|
|
791
856
|
# 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
857
|
def lexically_defined_method?(class_name, call_node, scope, kind, lexical_sites)
|
|
795
858
|
return false if lexical_sites.nil?
|
|
796
859
|
return true if lexical_sites.singleton_local_def?(call_node)
|
|
797
|
-
return false unless kind == :instance
|
|
798
860
|
|
|
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)
|
|
861
|
+
refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
805
862
|
end
|
|
806
863
|
|
|
807
|
-
#
|
|
808
|
-
#
|
|
809
|
-
#
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
864
|
+
# Is a refinement of `call_node`'s method name into `class_name` (or an ancestor) in effect at the call?
|
|
865
|
+
# Issue #1120 asks it for a method the class lacks. The two argument rules ask
|
|
866
|
+
# {#refined_redefinition_in_effect?} instead (issue #1740), and this only for a class-object receiver.
|
|
867
|
+
# Typing the call from the refine body is #1664.
|
|
868
|
+
# A refinement answers instance-side receivers only: a refined singleton (`refine X.singleton_class`) names
|
|
869
|
+
# no constant target, so nothing records it. Issue #1673 — the answer is derived from the file's in-effect
|
|
870
|
+
# refinements ({Inference::InEffectRefinements}), the list the typer reads, with each `using` expanded
|
|
871
|
+
# through its module's includes (issue #1671).
|
|
872
|
+
#
|
|
873
|
+
# ADR-121 WD7 — a module in effect whose refinements Rigor cannot read
|
|
874
|
+
# ({Inference::InEffectRefinements.opaque_module?}: a gem's, one from outside the analysed paths, one with an
|
|
875
|
+
# unreadable `refine`) may refine any class, a class object's included, so the call declines whatever its
|
|
876
|
+
# receiver. Issue #1796; both argument rules ask this predicate too.
|
|
877
|
+
def refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
878
|
+
return false if lexical_sites.nil?
|
|
879
|
+
return true if opaque_refinement_in_effect?(call_node, scope, lexical_sites)
|
|
813
880
|
|
|
814
|
-
modules =
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
881
|
+
modules =
|
|
882
|
+
if kind == :instance
|
|
883
|
+
Inference::InEffectRefinements.refining_modules(scope, class_name, call_node.name)
|
|
884
|
+
else
|
|
885
|
+
# ADR-121 WD7 — a row whose class the walk could not name (`refine(k)` with `k` a singleton class)
|
|
886
|
+
# reaches a class object too.
|
|
887
|
+
Inference::InEffectRefinements.class_unknown_refining_modules(scope, call_node.name)
|
|
888
|
+
end
|
|
889
|
+
!modules.nil? &&
|
|
890
|
+
lexical_sites.refinements.refinement_active?(call_node.location.start_offset, modules,
|
|
891
|
+
scope.declared_refinements) do |name|
|
|
892
|
+
Inference::InEffectRefinements.activated_modules(scope, name)
|
|
893
|
+
end
|
|
894
|
+
end
|
|
818
895
|
|
|
819
|
-
|
|
896
|
+
# Issues #1663, #1740 — the decline `call.wrong-arity` and `call.argument-type-mismatch` ask once a report
|
|
897
|
+
# would fire: a refinement that REDEFINES a method the class already has replaces the signature those rules
|
|
898
|
+
# check against, but Ruby finds a method the receiver's class (or a nearer ancestor) defines before it
|
|
899
|
+
# consults an ancestor's refinement (`refine Object do def center(a, b, c)` leaves `"x".center(1, 2, 3)` an
|
|
900
|
+
# `ArgumentError`). It stands on its own rather than behind {#refined_method_in_effect?}, whose ancestor
|
|
901
|
+
# reach declines `class Inc; def bar(a); include N; end` under `refine N do def bar(a, b, c)` (issue #1797).
|
|
902
|
+
#
|
|
903
|
+
# A call with nothing in effect is checked. Otherwise it reports only from positive knowledge: the list
|
|
904
|
+
# carries no unknown marker and no opaque module, and {Inference::RefinedDispatch::OwnMethod} proves the
|
|
905
|
+
# receiver's own definer answers first. A class-object receiver keeps {#refined_method_in_effect?}'s
|
|
906
|
+
# answer: its singleton-side levels are not walked yet.
|
|
907
|
+
def refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
908
|
+
return false if lexical_sites.nil?
|
|
909
|
+
|
|
910
|
+
list = refinement_list_at(call_node, scope, lexical_sites)
|
|
911
|
+
return false if list.empty?
|
|
912
|
+
return true if list.include?(Inference::InEffectRefinements::UNKNOWN)
|
|
913
|
+
return true if opaque_refinement_in_effect?(call_node, scope, lexical_sites)
|
|
914
|
+
return refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites) unless kind == :instance
|
|
915
|
+
|
|
916
|
+
!Inference::RefinedDispatch::OwnMethod.answers?(scope, class_name, call_node.name, list)
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
# The in-effect list at the call as the typer reads it: the lexical activations, each `using` expanded,
|
|
920
|
+
# then the plugin-declared modules (#1667).
|
|
921
|
+
def refinement_list_at(call_node, scope, lexical_sites)
|
|
922
|
+
query = lexical_sites.refinements
|
|
923
|
+
declared = scope.declared_refinements
|
|
924
|
+
return Inference::InEffectRefinements::EMPTY if query.empty? && declared.empty?
|
|
925
|
+
|
|
926
|
+
query.at(call_node.location.start_offset, declared) do |name|
|
|
927
|
+
Inference::InEffectRefinements.activated_modules(scope, name)
|
|
820
928
|
end
|
|
821
|
-
modules
|
|
822
929
|
end
|
|
823
930
|
|
|
824
|
-
|
|
825
|
-
|
|
931
|
+
# ADR-121 WD7 — does the call's in-effect list (its lexical activations, each `using` expanded; a
|
|
932
|
+
# plugin-declared module is the plugin's declaration and never opaque) hold an opaque module?
|
|
933
|
+
def opaque_refinement_in_effect?(call_node, scope, lexical_sites)
|
|
934
|
+
query = lexical_sites.refinements
|
|
935
|
+
return false if query.empty?
|
|
826
936
|
|
|
827
|
-
|
|
828
|
-
|
|
937
|
+
list = Inference::InEffectRefinements.lexical_list(scope, query, call_node.location.start_offset)
|
|
938
|
+
Inference::InEffectRefinements.opaque_in?(scope, list)
|
|
829
939
|
end
|
|
830
940
|
|
|
831
941
|
# The probes that run only once every cheaper answer has come back "absent", kept together
|
|
@@ -858,10 +968,25 @@ module Rigor
|
|
|
858
968
|
return true if module_mixin_receiver?(receiver_type, scope)
|
|
859
969
|
return true if mixin_self_class_receiver?(call_node, scope)
|
|
860
970
|
return true if unknown_mixin_includer?(class_name, scope)
|
|
971
|
+
return true if object_mixin_may_define?(receiver_type, class_name, call_node.name, scope)
|
|
861
972
|
|
|
862
973
|
ancestry_declares_method?(scope, class_name, call_node.name, kind)
|
|
863
974
|
end
|
|
864
975
|
|
|
976
|
+
# Issue #1697 — a module the project mixes into `Object` (a top-level `include M`, or `include M` in
|
|
977
|
+
# `class Object`) gives every object its instance methods, and the RBS lookup above only saw core's
|
|
978
|
+
# `Object`. A class object is an `Object` whatever class it is, so the singleton side always reaches
|
|
979
|
+
# it; the instance side does when the receiver's class descends from `Object`. A mixed-in module
|
|
980
|
+
# whose surface is unknown, or a `module_function` reached only privately, does not count here
|
|
981
|
+
# ({Inference::ObjectMixins.may_define?}).
|
|
982
|
+
def object_mixin_may_define?(receiver_type, class_name, method_name, scope)
|
|
983
|
+
unless receiver_type.is_a?(Type::Singleton) || Inference::ObjectMixins.reaches_object?(scope, class_name)
|
|
984
|
+
return false
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
Inference::ObjectMixins.may_define?(scope, method_name, receiver: :explicit)
|
|
988
|
+
end
|
|
989
|
+
|
|
865
990
|
# Issue #739 — the singleton twin of the module-mixin receiver, and the only shape that reaches the
|
|
866
991
|
# singleton side legitimately. `self.class` inside a mixin's instance method is the INCLUDER's class
|
|
867
992
|
# at runtime; the engine types it `Singleton[<the module>]`, one hop along from the same wrong
|
|
@@ -999,6 +1124,15 @@ module Rigor
|
|
|
999
1124
|
# 3. The standard `Kernel` / `Object` private-method
|
|
1000
1125
|
# surface (`puts`, `p`, `require`, `loop`, `raise`,
|
|
1001
1126
|
# …) drawn from the loaded RBS environment.
|
|
1127
|
+
# 4. The private singleton methods of the top-level `main`
|
|
1128
|
+
# object (`using`, `include`, `public`, `private`,
|
|
1129
|
+
# `define_method`), read from RBS core's
|
|
1130
|
+
# `RBS::Unnamed::TopLevelSelfClass`, plus
|
|
1131
|
+
# `ruby2_keywords` (issue #1383).
|
|
1132
|
+
# 5. The instance methods of a module the project mixes
|
|
1133
|
+
# into `Object` — a top-level `include M`, or an
|
|
1134
|
+
# `include M` in `class Object` — or any call at all
|
|
1135
|
+
# when such a module's surface is unknown (issue #1697).
|
|
1002
1136
|
#
|
|
1003
1137
|
# The rule deliberately does NOT generalise to
|
|
1004
1138
|
# implicit-self calls inside `def` / `class` / `module`
|
|
@@ -1010,7 +1144,7 @@ module Rigor
|
|
|
1010
1144
|
# Authored severity is `:warning`; the severity profile
|
|
1011
1145
|
# remaps it (`strict` → `:error`, `balanced` →
|
|
1012
1146
|
# `:warning`, `lenient` → `:off` / suppressed).
|
|
1013
|
-
def unresolved_toplevel_diagnostic(path, call_node, scope_index, eval_ranges = nil)
|
|
1147
|
+
def unresolved_toplevel_diagnostic(path, call_node, scope_index, eval_ranges = nil, lexical_sites = nil)
|
|
1014
1148
|
return nil unless call_node.receiver.nil?
|
|
1015
1149
|
|
|
1016
1150
|
scope = scope_index[call_node]
|
|
@@ -1021,15 +1155,38 @@ module Rigor
|
|
|
1021
1155
|
return nil if scope.top_level_def_for(name)
|
|
1022
1156
|
return nil if source_declared_method?(scope, "Object", name, :instance)
|
|
1023
1157
|
return nil if Rigor::Reflection.instance_method_definition("Object", name, scope: scope)
|
|
1158
|
+
return nil if main_singleton_method?(name, scope)
|
|
1159
|
+
return nil if Inference::ObjectMixins.may_define?(scope, name, receiver: :implicit)
|
|
1024
1160
|
# `Target.class_eval { def added = 1; def use_added = added }` files the defs on Target
|
|
1025
1161
|
# but leaves the eval body with a nil `self_type`, so `toplevel?` still holds. An eval
|
|
1026
1162
|
# body is morally a class body, so ADR-34 stays silent there — including on a genuinely
|
|
1027
1163
|
# undefined name inside the block. Ranges are computed once per file.
|
|
1028
1164
|
return nil if call_inside_receiver_eval_ranges?(eval_ranges, call_node)
|
|
1165
|
+
# ADR-121 WD7 — a refinement of `Object` (or one Rigor cannot read) in effect here may define the name.
|
|
1166
|
+
return nil if refined_method_in_effect?("Object", call_node, scope, :instance, lexical_sites)
|
|
1029
1167
|
|
|
1030
1168
|
build_unresolved_toplevel_diagnostic(path, call_node)
|
|
1031
1169
|
end
|
|
1032
1170
|
|
|
1171
|
+
# Issue #1383 — RBS core declares `main`'s private singleton methods on
|
|
1172
|
+
# `RBS::Unnamed::TopLevelSelfClass`, so the set follows RBS rather than a hand-kept
|
|
1173
|
+
# list. CRuby 4.0's `main` also has `ruby2_keywords`, which RBS does not declare.
|
|
1174
|
+
# rbs 3.x has no `TopLevelSelfClass`, so there only `ruby2_keywords` is covered.
|
|
1175
|
+
# A call inside a top-level block (`describe do include M end`) is judged here too;
|
|
1176
|
+
# silencing it is the cheaper error.
|
|
1177
|
+
MAIN_SINGLETON_CLASS_NAME = "RBS::Unnamed::TopLevelSelfClass"
|
|
1178
|
+
MAIN_SINGLETON_METHODS_MISSING_FROM_RBS = %i[ruby2_keywords].freeze
|
|
1179
|
+
private_constant :MAIN_SINGLETON_CLASS_NAME, :MAIN_SINGLETON_METHODS_MISSING_FROM_RBS
|
|
1180
|
+
|
|
1181
|
+
def main_singleton_method?(name, scope)
|
|
1182
|
+
return true if MAIN_SINGLETON_METHODS_MISSING_FROM_RBS.include?(name)
|
|
1183
|
+
# `TopLevelSelfClass < Object`: when the project's own RBS broke `Object`, building it would only
|
|
1184
|
+
# fail again and report a second, unfixable class name beside `Object` (#696's report).
|
|
1185
|
+
return false if Rigor::Reflection.instance_definition("Object", scope: scope).nil?
|
|
1186
|
+
|
|
1187
|
+
!Rigor::Reflection.instance_method_definition(MAIN_SINGLETON_CLASS_NAME, name, scope: scope).nil?
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1033
1190
|
# ScopeIndexer keeps RECEIVER_EVAL_CALLS private; this list is the same family
|
|
1034
1191
|
# (`class_eval` / `module_eval` / `class_exec` / `module_exec` / `instance_eval` /
|
|
1035
1192
|
# `instance_exec`). Used only to decline ADR-34 inside those blocks.
|
|
@@ -1305,8 +1462,11 @@ module Rigor
|
|
|
1305
1462
|
# - ADR-67 WD6b — an inferred-parameter receiver's type is an open-call-site lower bound, so firing
|
|
1306
1463
|
# undefined-method against it is a false positive by construction.
|
|
1307
1464
|
# - Issue #653 — a plugin answered this call site ({#plugin_typed_call?}).
|
|
1465
|
+
# - Issue #1717 — an explicit `self` receiver in a block whose `self` is unmodelled
|
|
1466
|
+
# ({#unknown_block_self_receiver?}).
|
|
1308
1467
|
def call_site_exempt?(call_node, scope)
|
|
1309
|
-
inferred_param_receiver?(call_node, scope) || plugin_typed_call?(call_node, scope)
|
|
1468
|
+
inferred_param_receiver?(call_node, scope) || plugin_typed_call?(call_node, scope) ||
|
|
1469
|
+
unknown_block_self_receiver?(call_node, scope)
|
|
1310
1470
|
end
|
|
1311
1471
|
|
|
1312
1472
|
# Issue #653 — true when a plugin's `dynamic_return` supplied the return type for THIS call node
|
|
@@ -1568,7 +1728,7 @@ module Rigor
|
|
|
1568
1728
|
# when the call's receiver / RBS coverage / call shape
|
|
1569
1729
|
# disqualifies the rule.
|
|
1570
1730
|
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1571
|
-
def wrong_arity_diagnostic(path, call_node, scope_index)
|
|
1731
|
+
def wrong_arity_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
1572
1732
|
return nil if call_node.receiver.nil?
|
|
1573
1733
|
return nil unless plain_positional_call?(call_node)
|
|
1574
1734
|
|
|
@@ -1609,11 +1769,18 @@ module Rigor
|
|
|
1609
1769
|
source_arity&.settle_by_definitions
|
|
1610
1770
|
return nil
|
|
1611
1771
|
end
|
|
1772
|
+
return nil if arity_report_withheld?(source_arity, class_name, call_node, scope, kind, lexical_sites)
|
|
1773
|
+
|
|
1774
|
+
build_arity_diagnostic(path, call_node, class_name, min, max, actual)
|
|
1775
|
+
end
|
|
1776
|
+
|
|
1777
|
+
# The declines `call.wrong-arity` asks only once the arity is out of the envelope.
|
|
1778
|
+
def arity_report_withheld?(source_arity, class_name, call_node, scope, kind, lexical_sites)
|
|
1612
1779
|
# Issue #992 — a source envelope's remaining declines can only withhold, so they run only now.
|
|
1613
1780
|
source_arity&.settle_by_walk
|
|
1614
|
-
return
|
|
1781
|
+
return true if source_arity && !source_arity.authoritative?(class_name)
|
|
1615
1782
|
|
|
1616
|
-
|
|
1783
|
+
refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
1617
1784
|
end
|
|
1618
1785
|
|
|
1619
1786
|
# The `[min, max]` the call is checked against — a declared signature's, or (issue #992) the project
|
|
@@ -1813,7 +1980,7 @@ module Rigor
|
|
|
1813
1980
|
# and union receivers where every member already
|
|
1814
1981
|
# disqualifies the call (avoid duplicating the
|
|
1815
1982
|
# undefined-method diagnostic).
|
|
1816
|
-
def nil_receiver_diagnostic(path, call_node, scope_index)
|
|
1983
|
+
def nil_receiver_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
1817
1984
|
return nil if call_node.receiver.nil?
|
|
1818
1985
|
# Safe-navigation calls (`recv&.method`) already
|
|
1819
1986
|
# short-circuit on nil at runtime, so a nil-bearing
|
|
@@ -1862,10 +2029,22 @@ module Rigor
|
|
|
1862
2029
|
return nil unless Rigor::Reflection.rbs_class_known?("NilClass", scope: scope)
|
|
1863
2030
|
|
|
1864
2031
|
return nil unless nil_bearing_union_witnesses?(receiver_type, call_node.name, scope)
|
|
2032
|
+
return nil if nil_receiver_withheld?(call_node, receiver_type, scope, scope_index, lexical_sites)
|
|
1865
2033
|
|
|
1866
2034
|
build_nil_receiver_diagnostic(path, call_node)
|
|
1867
2035
|
end
|
|
1868
2036
|
|
|
2037
|
+
# The declines `call.possible-nil-receiver` asks only once a report would fire.
|
|
2038
|
+
def nil_receiver_withheld?(call_node, receiver_type, scope, scope_index, lexical_sites)
|
|
2039
|
+
# Issue #1703 — a `nil` that only a `key?` guard on the same receiver and key rules out. It re-walks the file
|
|
2040
|
+
# once with guards on, and only to withhold this report.
|
|
2041
|
+
return true if Inference::KeyPresenceGuard.withholds_nil?(call_node, receiver_type, lexical_sites&.root,
|
|
2042
|
+
scope_index)
|
|
2043
|
+
|
|
2044
|
+
# ADR-121 WD7 — a refinement of `NilClass` (or one Rigor cannot read) in effect may define the name.
|
|
2045
|
+
refined_method_in_effect?("NilClass", call_node, scope, :instance, lexical_sites)
|
|
2046
|
+
end
|
|
2047
|
+
|
|
1869
2048
|
# The receiver-type half of the rule, factored out of the node-shape
|
|
1870
2049
|
# guards above: the union must carry nil, must carry a non-nil arm the
|
|
1871
2050
|
# presence question can be asked of, must support the method on every
|
|
@@ -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,44 @@ 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
|
+
refined_method_in_effect?(concrete_class_name(member), call_node, scope, :instance, lexical_sites)
|
|
2199
|
+
end
|
|
2200
|
+
end
|
|
2201
|
+
|
|
2202
|
+
# Issue #1699 — a union of one class's literals, alone or beside that class's plain nominal
|
|
2203
|
+
# (`0 | Integer`, which `gets.to_i` types; `"a" | "b"`; `1 | 2`), holds nothing but instances of
|
|
2204
|
+
# that class, so it answers a method-existence question exactly as the class does. The union rule
|
|
2205
|
+
# above declines it through its distinct-class guard, which exists for SHAPE joins
|
|
2206
|
+
# (`Hash[K1, V1] | Hash[K2, V2]`, a corpus misinference on mail), and the scalar rule declined it
|
|
2207
|
+
# for having no single concrete class, so neither judged it. Returns that class's nominal, or `type`
|
|
2208
|
+
# itself. Literal-and-plain-nominal arms only: a generic, shape or refinement arm keeps the union
|
|
2209
|
+
# declined, and so does a union spanning two classes.
|
|
2210
|
+
def single_class_literal_union(type)
|
|
2211
|
+
return type unless type.is_a?(Type::Union)
|
|
2212
|
+
|
|
2213
|
+
members = type.members
|
|
2214
|
+
return type unless members.any?(Type::Constant)
|
|
2215
|
+
return type unless members.all? do |member|
|
|
2216
|
+
member.is_a?(Type::Constant) || (member.is_a?(Type::Nominal) && member.type_args.empty?)
|
|
2217
|
+
end
|
|
2218
|
+
|
|
2219
|
+
class_names = members.map { |member| concrete_class_name(member) }.uniq
|
|
2220
|
+
return type unless class_names.size == 1
|
|
2221
|
+
|
|
2222
|
+
class_name = class_names.first
|
|
2223
|
+
return type if class_name.nil? || class_name == "NilClass"
|
|
2224
|
+
|
|
2225
|
+
Type::Combinator.nominal_of(class_name)
|
|
2226
|
+
end
|
|
2227
|
+
|
|
2013
2228
|
# An arm that makes a sound "undefined on every arm" verdict impossible: a non-class surface
|
|
2014
2229
|
# (Dynamic / Top / Bot), a singleton (slice 1 reasons about instance arms only), the generic
|
|
2015
2230
|
# metaclass `Class` / `Module` (a value typed as one is *some* class/module object whose singleton
|
|
@@ -2150,9 +2365,13 @@ module Rigor
|
|
|
2150
2365
|
INTEGER_RAISING_OPERATORS = %i[/ % div modulo divmod].freeze
|
|
2151
2366
|
private_constant :INTEGER_RAISING_OPERATORS
|
|
2152
2367
|
|
|
2153
|
-
def always_raises_diagnostic(path, call_node, scope_index)
|
|
2368
|
+
def always_raises_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
2154
2369
|
return nil unless integer_zero_division?(call_node, scope_index)
|
|
2155
2370
|
|
|
2371
|
+
# ADR-121 WD7 — a refinement of `Integer`'s operator (or one Rigor cannot read) in effect may not raise.
|
|
2372
|
+
scope = scope_index[call_node]
|
|
2373
|
+
return nil if refined_method_in_effect?("Integer", call_node, scope, :instance, lexical_sites)
|
|
2374
|
+
|
|
2156
2375
|
build_always_raises_diagnostic(path, call_node)
|
|
2157
2376
|
end
|
|
2158
2377
|
|
|
@@ -2493,7 +2712,7 @@ module Rigor
|
|
|
2493
2712
|
names = [method_name, *GLOBAL_WRITE_HATCHES]
|
|
2494
2713
|
census = global_write_census(scope)
|
|
2495
2714
|
program_may_answer?(ancestors, names, census, scope) ||
|
|
2496
|
-
(method_name == :write && refined_writer_in_effect?(census, node, lexical_sites))
|
|
2715
|
+
(method_name == :write && refined_writer_in_effect?(census, node, scope, lexical_sites))
|
|
2497
2716
|
end
|
|
2498
2717
|
|
|
2499
2718
|
def overridden_hatch?(definition)
|
|
@@ -2549,9 +2768,10 @@ module Rigor
|
|
|
2549
2768
|
# refine(T)`), so a refined `write` on any class declines every literal. `respond_to?(:write)` honours an
|
|
2550
2769
|
# active refinement on Ruby 4.0.5; a refined `respond_to_missing?` or `respond_to?`, and every implicit
|
|
2551
2770
|
# conversion, do not, so a refinement that adds only those leaves the write reported.
|
|
2552
|
-
def refined_writer_in_effect?(census, node, lexical_sites)
|
|
2771
|
+
def refined_writer_in_effect?(census, node, scope, lexical_sites)
|
|
2553
2772
|
Inference::GlobalWriteCensus.refines_write?(census) &&
|
|
2554
|
-
(lexical_sites.nil? ||
|
|
2773
|
+
(lexical_sites.nil? || !scope.declared_refinements.empty? ||
|
|
2774
|
+
lexical_sites.refinements.any_at?(node.location.start_offset))
|
|
2555
2775
|
end
|
|
2556
2776
|
|
|
2557
2777
|
def build_global_write_type_diagnostic(path, node, contract, class_name)
|
|
@@ -2634,7 +2854,7 @@ module Rigor
|
|
|
2634
2854
|
# - Issue #1568 — a module the class PREPENDS sits ahead of it in Ruby's order, so a definition there
|
|
2635
2855
|
# is what the call reaches; the class's own private `def` answers only when nothing prepended
|
|
2636
2856
|
# defines the name ({#reached_definer_visibility}).
|
|
2637
|
-
def visibility_mismatch_diagnostic(path, call_node, scope_index)
|
|
2857
|
+
def visibility_mismatch_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
2638
2858
|
return nil unless explicit_non_self_receiver?(call_node.receiver)
|
|
2639
2859
|
|
|
2640
2860
|
scope = scope_index[call_node]
|
|
@@ -2653,6 +2873,8 @@ module Rigor
|
|
|
2653
2873
|
reached = reached_definer_visibility(scope, receiver_type.class_name, call_node.name)
|
|
2654
2874
|
return nil if reached == :undecided
|
|
2655
2875
|
return nil unless reached.nil? || reached == :private
|
|
2876
|
+
# ADR-121 WD7 — a refinement in effect (or one Rigor cannot read) may redefine the method, public.
|
|
2877
|
+
return nil if refined_method_in_effect?(receiver_type.class_name, call_node, scope, :instance, lexical_sites)
|
|
2656
2878
|
|
|
2657
2879
|
build_visibility_mismatch_diagnostic(path, call_node, receiver_type)
|
|
2658
2880
|
end
|
|
@@ -2905,7 +3127,7 @@ module Rigor
|
|
|
2905
3127
|
COERCE_DISPATCH_METHODS = %i[+ - * / % ** & | ^ << >> < > <= >=].to_set.freeze
|
|
2906
3128
|
private_constant :COERCE_DISPATCH_METHODS
|
|
2907
3129
|
|
|
2908
|
-
def argument_type_diagnostic(path, call_node, scope_index)
|
|
3130
|
+
def argument_type_diagnostic(path, call_node, scope_index, lexical_sites = nil)
|
|
2909
3131
|
return nil if call_node.receiver.nil?
|
|
2910
3132
|
return nil if UNIVERSAL_EQUALITY_METHODS.include?(call_node.name)
|
|
2911
3133
|
return nil unless plain_positional_call?(call_node)
|
|
@@ -2932,11 +3154,19 @@ module Rigor
|
|
|
2932
3154
|
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def, environment: scope.environment)
|
|
2933
3155
|
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, scope_index)
|
|
2934
3156
|
return nil if mismatch.nil?
|
|
2935
|
-
return nil if
|
|
3157
|
+
return nil if argument_report_withheld?(receiver_type, class_name, call_node, mismatch, scope, lexical_sites)
|
|
2936
3158
|
|
|
2937
3159
|
build_argument_type_diagnostic(path, call_node, class_name, mismatch)
|
|
2938
3160
|
end
|
|
2939
3161
|
|
|
3162
|
+
# The declines `call.argument-type-mismatch` asks only once an argument mismatches.
|
|
3163
|
+
def argument_report_withheld?(receiver_type, class_name, call_node, mismatch, scope, lexical_sites)
|
|
3164
|
+
return true if inferred_param_mismatch_verdict?(call_node, mismatch, scope)
|
|
3165
|
+
|
|
3166
|
+
kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
|
|
3167
|
+
refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
3168
|
+
end
|
|
3169
|
+
|
|
2940
3170
|
# ADR-67 WD6b — an argument-type-mismatch verdict resting on an open-call-site lower bound, on
|
|
2941
3171
|
# either side of the call. The ARGUMENT side: the mismatching argument is an inferred-parameter
|
|
2942
3172
|
# local, so firing against it is an FP by construction. The RECEIVER side: when the receiver roots
|
|
@@ -3738,6 +3968,7 @@ module Rigor
|
|
|
3738
3968
|
# silent.
|
|
3739
3969
|
def override_visibility_diagnostic(path, def_node, scope_index)
|
|
3740
3970
|
return nil unless def_node.receiver.nil? # instance methods only
|
|
3971
|
+
return nil if IMPLICITLY_PRIVATE_METHODS.include?(def_node.name)
|
|
3741
3972
|
|
|
3742
3973
|
scope = scope_index[def_node]
|
|
3743
3974
|
return nil if scope.nil?
|
|
@@ -3869,6 +4100,7 @@ module Rigor
|
|
|
3869
4100
|
# fire) when any gate is unmet.
|
|
3870
4101
|
def resolve_authored_override(def_node, scope_index)
|
|
3871
4102
|
return nil unless def_node.receiver.nil? # instance methods only (singleton: follow-on)
|
|
4103
|
+
return nil if LIFECYCLE_HOOKS.include?(def_node.name) # ADR-35 WD10: outside substitutability
|
|
3872
4104
|
|
|
3873
4105
|
scope = scope_index[def_node]
|
|
3874
4106
|
return nil if scope.nil?
|