rigortype 0.4.2 → 0.4.4
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/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +28 -5
- data/docs/manual/02-cli-reference.md +23 -4
- data/docs/manual/03-configuration.md +21 -8
- data/docs/manual/04-diagnostics.md +44 -2
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +32 -3
- 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 +26 -9
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/rule_ids.rb +64 -0
- data/lib/rigor/analysis/check_rules.rb +68 -57
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/incremental.rb +12 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +3 -3
- data/lib/rigor/analysis/incremental_session.rb +207 -40
- 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 +139 -15
- data/lib/rigor/analysis/reachability/scan.rb +374 -173
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +6 -1
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +6 -4
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +74 -13
- data/lib/rigor/analysis/runner/pool_coordinator.rb +133 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +46 -1
- data/lib/rigor/analysis/runner.rb +62 -18
- data/lib/rigor/analysis/severity_stamp.rb +34 -1
- data/lib/rigor/analysis/worker_session.rb +58 -2
- data/lib/rigor/cache/descriptor.rb +40 -6
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +56 -12
- data/lib/rigor/cli/check_command.rb +138 -39
- 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/mcp_command.rb +1 -1
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +157 -24
- data/lib/rigor/cli.rb +30 -4
- data/lib/rigor/config_audit.rb +111 -15
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration.rb +15 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- 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_loader.rb +79 -10
- data/lib/rigor/environment/required_features.rb +4 -2
- data/lib/rigor/environment.rb +12 -5
- data/lib/rigor/inference/block_call_timing.rb +5 -0
- data/lib/rigor/inference/builtins/cruby_definers.rb +22 -5
- data/lib/rigor/inference/captured_locals.rb +69 -6
- data/lib/rigor/inference/definer_resolution.rb +16 -7
- data/lib/rigor/inference/expression_typer.rb +117 -29
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +21 -17
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- 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/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -16
- 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 +52 -3
- data/lib/rigor/inference/module_function_state.rb +80 -13
- data/lib/rigor/inference/narrowing.rb +184 -68
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +38 -24
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +112 -40
- data/lib/rigor/inference/refined_dispatch.rb +62 -14
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +416 -1
- data/lib/rigor/inference/scope_indexer.rb +565 -61
- data/lib/rigor/inference/self_call_ivar_writes.rb +341 -0
- data/lib/rigor/inference/statement_evaluator.rb +307 -60
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/language_server/project_context.rb +56 -5
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +133 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +802 -0
- data/lib/rigor/lens/members.rb +203 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +322 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +41 -0
- data/lib/rigor/mcp/server.rb +61 -20
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/project_environment.rb +23 -1
- data/lib/rigor/protection/mutation_cache.rb +2 -1
- data/lib/rigor/scope.rb +33 -17
- data/lib/rigor/sig_gen/generator.rb +45 -5
- data/lib/rigor/sig_gen/layout_index.rb +36 -0
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/writer.rb +47 -34
- data/lib/rigor/version.rb +1 -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-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +28 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- data/sig/rigor/scope.rbs +3 -0
- data/sig/rigor.rbs +6 -3
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +21 -1
|
@@ -156,6 +156,13 @@ module Rigor
|
|
|
156
156
|
# first segment appears here as KNOWN and stays silent: these ids are legitimate suppression /
|
|
157
157
|
# `severity_overrides:` vocabulary the light rule-id table cannot enumerate (plugins load dynamically;
|
|
158
158
|
# aggregator ids live in the engine-heavy runner), so under-warning is the FP-safe direction.
|
|
159
|
+
# The `pre-eval.*` ids (ADR-17 WD6) the bare family wildcard `pre-eval` expands to. Emitted by the
|
|
160
|
+
# `pre_eval:` pre-pass, outside the CheckRules catalogue, so the family cannot ride `RULE_FAMILIES`
|
|
161
|
+
# (which also drives the catalogue and the config audit over `ALL_RULES`).
|
|
162
|
+
PRE_EVAL_RULES = %w[
|
|
163
|
+
pre-eval.parse-error pre-eval.file-not-found pre-eval.duplicate-declaration
|
|
164
|
+
].freeze
|
|
165
|
+
|
|
159
166
|
NON_CHECK_DIAGNOSTIC_FAMILIES = %w[rbs_extended dynamic rbs pre-eval plugin plugin_trust].freeze
|
|
160
167
|
|
|
161
168
|
# Bare (dot-less) diagnostic ids the engine emits outside the catalogue (see the `rule:` literals in
|
|
@@ -166,6 +173,63 @@ module Rigor
|
|
|
166
173
|
configuration-error load-error pool-degraded runtime-error source-rbs-synthesis-failed
|
|
167
174
|
source-rbs-annotation-not-honoured
|
|
168
175
|
].freeze
|
|
176
|
+
|
|
177
|
+
# Resolves a user-supplied rule token (`undefined-method`, `call.undefined-method`, or the family wildcard
|
|
178
|
+
# `call`) to the set of canonical rule identifiers it disables. Returns `nil` for `"all"` (the existing
|
|
179
|
+
# wildcard meaning "every rule"), or for unknown tokens.
|
|
180
|
+
def self.resolve_rule_token(token)
|
|
181
|
+
return nil if token == "all"
|
|
182
|
+
return [LEGACY_RULE_ALIASES.fetch(token)] if LEGACY_RULE_ALIASES.key?(token)
|
|
183
|
+
return PRE_EVAL_RULES if token == "pre-eval"
|
|
184
|
+
return ALL_RULES.select { |r| r.start_with?("#{token}.") } if RULE_FAMILIES.include?(token)
|
|
185
|
+
|
|
186
|
+
ALL_RULES.include?(token) ? [token] : []
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Expands a list of user-supplied rule tokens into the canonical-id set per ADR-8 § "Backward
|
|
190
|
+
# compatibility". `disabled_rules` accepts unprefixed legacy names (`undefined-method`), canonical names
|
|
191
|
+
# (`call.undefined-method`), family wildcards (`call`) and the `all` sentinel.
|
|
192
|
+
def self.expand_rule_tokens(tokens)
|
|
193
|
+
Array(tokens).each_with_object(Set.new) do |token, set|
|
|
194
|
+
set.merge(expand_token(token.to_s))
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def self.expand_token(token)
|
|
199
|
+
return ["all"] if token == "all"
|
|
200
|
+
|
|
201
|
+
resolved = resolve_rule_token(token)
|
|
202
|
+
resolved.nil? || resolved.empty? ? [token] : resolved
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Whether the `.rigor.yml` `disable:` list, expanded by {.expand_rule_tokens}, drops a diagnostic whose rule
|
|
206
|
+
# id is `rule`: the `all` sentinel drops every rule-bearing row, any other entry its exact id. A nil rule (a
|
|
207
|
+
# parse, path or internal error) is never dropped.
|
|
208
|
+
def self.config_disabled?(rule, disabled)
|
|
209
|
+
return false if rule.nil?
|
|
210
|
+
|
|
211
|
+
disabled.include?("all") || disabled.include?(rule)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# The source-family prefix the runner stamps on every plugin-emitted row (`plugin.<id>`).
|
|
215
|
+
PLUGIN_FAMILY_PREFIX = "plugin."
|
|
216
|
+
|
|
217
|
+
# Issue #1953 — whether a suppression token set drops a plugin's own row. Such a row answers only to its
|
|
218
|
+
# qualified id: the exact `plugin.<id>.<rule>` (its `qualified_rule`) or the whole plugin's `plugin.<id>`
|
|
219
|
+
# (its `source_family`). Both compare whole strings against the row's own fields, so `plugin.<id>` never
|
|
220
|
+
# reaches a plugin whose id merely starts with `<id>.`. Ids and rules may both hold dots, so plugin `a` with
|
|
221
|
+
# rule `b.c` and plugin `a.b` with rule `c` share `plugin.a.b.c`, and that exact token drops both. A bare rule
|
|
222
|
+
# and `all` do not reach these rows: many plugins share a bare rule (`load-error`, `unknown-column`), and a
|
|
223
|
+
# bare match would hide another plugin's findings. Every other row (built-in, `plugin_loader`, `plugin_trust`)
|
|
224
|
+
# answers false.
|
|
225
|
+
def self.plugin_row_disabled?(diagnostic, tokens)
|
|
226
|
+
return false if diagnostic.rule.nil?
|
|
227
|
+
|
|
228
|
+
family = diagnostic.source_family.to_s
|
|
229
|
+
return false unless family.start_with?(PLUGIN_FAMILY_PREFIX)
|
|
230
|
+
|
|
231
|
+
tokens.include?(family) || tokens.include?(diagnostic.qualified_rule)
|
|
232
|
+
end
|
|
169
233
|
end
|
|
170
234
|
end
|
|
171
235
|
end
|
|
@@ -79,18 +79,8 @@ module Rigor
|
|
|
79
79
|
# runtime one and `def.override-visibility-reduced` cannot prove a reduction.
|
|
80
80
|
IMPLICITLY_PRIVATE_METHODS = (LIFECYCLE_HOOKS + %i[respond_to_missing?]).freeze
|
|
81
81
|
|
|
82
|
-
#
|
|
83
|
-
# `
|
|
84
|
-
# to the set of canonical rule identifiers it disables.
|
|
85
|
-
# Returns `nil` for `"all"` (the existing wildcard meaning
|
|
86
|
-
# "every rule"), or for unknown tokens.
|
|
87
|
-
def self.resolve_rule_token(token)
|
|
88
|
-
return nil if token == "all"
|
|
89
|
-
return [LEGACY_RULE_ALIASES.fetch(token)] if LEGACY_RULE_ALIASES.key?(token)
|
|
90
|
-
return ALL_RULES.select { |r| r.start_with?("#{token}.") } if RULE_FAMILIES.include?(token)
|
|
91
|
-
|
|
92
|
-
ALL_RULES.include?(token) ? [token] : []
|
|
93
|
-
end
|
|
82
|
+
# The token expansion (`resolve_rule_token` / `expand_rule_tokens` / `expand_token`) lives in the light
|
|
83
|
+
# `check_rules/rule_ids.rb`, so {SeverityStamp} can apply the `disable:` list on the engine-free hit path.
|
|
94
84
|
|
|
95
85
|
module_function
|
|
96
86
|
|
|
@@ -479,7 +469,7 @@ module Rigor
|
|
|
479
469
|
# - **File-level** (v0.1.2): `# rigor:disable-file <rule1>,
|
|
480
470
|
# <rule2>` anywhere in the file suppresses the matching
|
|
481
471
|
# diagnostic for every line. `# rigor:disable-file all`
|
|
482
|
-
# suppresses every rule across the file. Convention is
|
|
472
|
+
# suppresses every built-in rule across the file. Convention is
|
|
483
473
|
# to put the comment near the top, but Rigor accepts it
|
|
484
474
|
# anywhere — the comment scope is "this file" regardless
|
|
485
475
|
# of position.
|
|
@@ -487,7 +477,8 @@ module Rigor
|
|
|
487
477
|
# on the same line as the offending expression
|
|
488
478
|
# suppresses the matching diagnostic for that line
|
|
489
479
|
# only. `# rigor:disable all` on a line suppresses
|
|
490
|
-
# every rule on that line.
|
|
480
|
+
# every built-in rule on that line. A plugin's rows are
|
|
481
|
+
# not passed here; see {.filter_plugin_suppressed}.
|
|
491
482
|
#
|
|
492
483
|
# Both in-source forms are recognised only when the
|
|
493
484
|
# marker is the FIRST thing in the comment, so a
|
|
@@ -506,7 +497,7 @@ module Rigor
|
|
|
506
497
|
diagnostics.reject do |diagnostic|
|
|
507
498
|
rule = diagnostic.rule
|
|
508
499
|
next false if rule.nil?
|
|
509
|
-
next true if
|
|
500
|
+
next true if config_disabled?(rule, disabled)
|
|
510
501
|
next true if file_suppressions.include?("all") || file_suppressions.include?(rule)
|
|
511
502
|
|
|
512
503
|
line_rules = line_suppressions[diagnostic.line]
|
|
@@ -514,6 +505,26 @@ module Rigor
|
|
|
514
505
|
end
|
|
515
506
|
end
|
|
516
507
|
|
|
508
|
+
# Issue #1953 — the in-source markers over the rows a plugin emitted while `path` was analysed
|
|
509
|
+
# (`#diagnostics_for_file` and node rules). A row answers to a `plugin.<id>.<rule>` or `plugin.<id>` token
|
|
510
|
+
# only ({.plugin_row_disabled?}); a bare id and `all` keep reaching built-in rows alone. `comments` are
|
|
511
|
+
# `path`'s, so a row the plugin positioned in another file is left to the `disable:` list and the baseline.
|
|
512
|
+
# The `disable:` list itself is applied to these rows by {SeverityStamp}, on every run shape.
|
|
513
|
+
def filter_plugin_suppressed(diagnostics, path:, comments:)
|
|
514
|
+
return diagnostics if diagnostics.empty?
|
|
515
|
+
|
|
516
|
+
line_suppressions, file_suppressions = parse_suppression_comments(comments)
|
|
517
|
+
return diagnostics if line_suppressions.empty? && file_suppressions.empty?
|
|
518
|
+
|
|
519
|
+
diagnostics.reject do |diagnostic|
|
|
520
|
+
next false unless diagnostic.path == path
|
|
521
|
+
next true if plugin_row_disabled?(diagnostic, file_suppressions)
|
|
522
|
+
|
|
523
|
+
line_rules = line_suppressions.fetch(diagnostic.line, nil)
|
|
524
|
+
!line_rules.nil? && plugin_row_disabled?(diagnostic, line_rules)
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
517
528
|
# Every suppression-recognition pattern below is anchored with `\A` against the COMMENT SLICE —
|
|
518
529
|
# Prism hands us a slice that starts at the `#`, so `\A#` means "the marker is the first thing in
|
|
519
530
|
# the comment". A real directive always has that shape, in both the whole-line form and the
|
|
@@ -654,7 +665,7 @@ module Rigor
|
|
|
654
665
|
# `dynamic.*`, `rbs.*`, `pre-eval.*`, and any `plugin.`-prefixed id — plugins load dynamically, so
|
|
655
666
|
# their rule vocabulary cannot be enumerated here and under-warning is the FP-safe direction).
|
|
656
667
|
def known_suppression_token?(token)
|
|
657
|
-
return true if token
|
|
668
|
+
return true if %w[all pre-eval].include?(token)
|
|
658
669
|
return true if ALL_RULES.include?(token) || LEGACY_RULE_ALIASES.key?(token) ||
|
|
659
670
|
RULE_FAMILIES.include?(token) || NON_CHECK_DIAGNOSTIC_IDS.include?(token)
|
|
660
671
|
|
|
@@ -703,24 +714,6 @@ module Rigor
|
|
|
703
714
|
)
|
|
704
715
|
end
|
|
705
716
|
|
|
706
|
-
# Expands a list of user-supplied rule tokens into the
|
|
707
|
-
# canonical-id set per ADR-8 § "Backward compatibility".
|
|
708
|
-
# `disabled_rules` accepts unprefixed legacy names
|
|
709
|
-
# (`undefined-method`), canonical names
|
|
710
|
-
# (`call.undefined-method`), and family wildcards (`call`).
|
|
711
|
-
def expand_rule_tokens(tokens)
|
|
712
|
-
Array(tokens).each_with_object(Set.new) do |token, set|
|
|
713
|
-
set.merge(expand_token(token.to_s))
|
|
714
|
-
end
|
|
715
|
-
end
|
|
716
|
-
|
|
717
|
-
def expand_token(token)
|
|
718
|
-
return ["all"] if token == "all"
|
|
719
|
-
|
|
720
|
-
resolved = resolve_rule_token(token)
|
|
721
|
-
resolved.nil? || resolved.empty? ? [token] : resolved
|
|
722
|
-
end
|
|
723
|
-
|
|
724
717
|
# rubocop:disable-next Metrics/ClassLength
|
|
725
718
|
class << self
|
|
726
719
|
private
|
|
@@ -863,29 +856,23 @@ module Rigor
|
|
|
863
856
|
|
|
864
857
|
# Is a refinement of `call_node`'s method name into `class_name` (or an ancestor) in effect at the call?
|
|
865
858
|
# Issue #1120 asks it for a method the class lacks. The two argument rules ask
|
|
866
|
-
# {#refined_redefinition_in_effect?} instead (issue #1740)
|
|
867
|
-
#
|
|
868
|
-
#
|
|
869
|
-
#
|
|
870
|
-
#
|
|
871
|
-
#
|
|
859
|
+
# {#refined_redefinition_in_effect?} instead (issue #1740). Typing the call from the refine body is #1664.
|
|
860
|
+
# Issue #1752 — a class-object receiver (`kind` `:singleton`) is reached by a refinement of any class or module
|
|
861
|
+
# on its lookup: `Class`, `Module`, `Object`, `Kernel`, `BasicObject`, or a module it extends. A refined
|
|
862
|
+
# singleton class (`refine X.singleton_class`) names no constant target, so its rows are class-unknown, which
|
|
863
|
+
# reach every receiver. Issue #1673 — the answer is derived from the file's in-effect refinements
|
|
864
|
+
# ({Inference::InEffectRefinements}), the list the typer reads, with each `using` expanded through its
|
|
865
|
+
# module's includes (issue #1671).
|
|
872
866
|
#
|
|
873
867
|
# ADR-121 WD7 — a module in effect whose refinements Rigor cannot read
|
|
874
868
|
# ({Inference::InEffectRefinements.opaque_module?}: a gem's, one from outside the analysed paths, one with an
|
|
875
869
|
# unreadable `refine`) may refine any class, a class object's included, so the call declines whatever its
|
|
876
|
-
# receiver. Issue #1796
|
|
870
|
+
# receiver. Issue #1796.
|
|
877
871
|
def refined_method_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
878
872
|
return false if lexical_sites.nil?
|
|
879
873
|
return true if opaque_refinement_in_effect?(call_node, scope, lexical_sites)
|
|
880
874
|
|
|
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
|
|
875
|
+
modules = Inference::InEffectRefinements.refining_modules(scope, class_name, call_node.name, kind)
|
|
889
876
|
!modules.nil? &&
|
|
890
877
|
lexical_sites.refinements.refinement_active?(call_node.location.start_offset, modules,
|
|
891
878
|
scope.declared_refinements) do |name|
|
|
@@ -902,8 +889,8 @@ module Rigor
|
|
|
902
889
|
#
|
|
903
890
|
# A call with nothing in effect is checked. Otherwise it reports only from positive knowledge: the list
|
|
904
891
|
# carries no unknown marker and no opaque module, and {Inference::RefinedDispatch::OwnMethod} proves the
|
|
905
|
-
# receiver's own definer answers first.
|
|
906
|
-
#
|
|
892
|
+
# receiver's own definer answers first. Issue #1752 — a class-object receiver (`kind` `:singleton`) walks its
|
|
893
|
+
# singleton levels: `Integer.sqrt(1, 2)` under `refine Object do def sqrt(a, b)` runs `Integer.sqrt` and raises.
|
|
907
894
|
def refined_redefinition_in_effect?(class_name, call_node, scope, kind, lexical_sites)
|
|
908
895
|
return false if lexical_sites.nil?
|
|
909
896
|
|
|
@@ -911,9 +898,8 @@ module Rigor
|
|
|
911
898
|
return false if list.empty?
|
|
912
899
|
return true if list.include?(Inference::InEffectRefinements::UNKNOWN)
|
|
913
900
|
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
901
|
|
|
916
|
-
!Inference::RefinedDispatch::OwnMethod.answers?(scope, class_name, call_node.name, list)
|
|
902
|
+
!Inference::RefinedDispatch::OwnMethod.answers?(scope, class_name, call_node.name, list, kind: kind)
|
|
917
903
|
end
|
|
918
904
|
|
|
919
905
|
# The in-effect list at the call as the typer reads it: the lexical activations, each `using` expanded,
|
|
@@ -1399,6 +1385,21 @@ module Rigor
|
|
|
1399
1385
|
GEM_OVERLAY_OPEN_RECEIVERS = Set["ActiveSupport::Duration", "ActiveSupport::Concern"].freeze
|
|
1400
1386
|
private_constant :GEM_OVERLAY_OPEN_RECEIVERS
|
|
1401
1387
|
|
|
1388
|
+
# Issue #1456 — the `data/core_overlay/` twin of {GEM_OVERLAY_OPEN_RECEIVERS}. `OpenStruct` defines each field
|
|
1389
|
+
# as a per-instance singleton method and answers the rest through a private `method_missing` (ostruct 0.6), so
|
|
1390
|
+
# no signature can enumerate its surface. The only other declaration in the environment is json's add-on
|
|
1391
|
+
# reopening (`json_create` / `as_json` / `to_json`), which made the class read as fully declared and every
|
|
1392
|
+
# field access a `call.undefined-method`. `data/core_overlay/ostruct.rbs` declares that `method_missing`, and
|
|
1393
|
+
# an RBS-declared `method_missing` does not by itself open a receiver here, so the class is named. No load
|
|
1394
|
+
# gate is needed: the core overlay loads into every environment that has RBS at all, so the list is active
|
|
1395
|
+
# whenever the declaration it protects is. Both sides of the class are open, as with
|
|
1396
|
+
# {GEM_OVERLAY_OPEN_RECEIVERS}, and an inherited Object / Kernel signature stops deciding `call.wrong-arity`
|
|
1397
|
+
# and `call.argument-type-mismatch` ({#unauthoritative_inherited_signature?}): lost true positives,
|
|
1398
|
+
# accepted. The general rule — a class declared only by another library's partial reopening — is #1978, not
|
|
1399
|
+
# this list.
|
|
1400
|
+
CORE_OVERLAY_OPEN_RECEIVERS = Set["OpenStruct"].freeze
|
|
1401
|
+
private_constant :CORE_OVERLAY_OPEN_RECEIVERS
|
|
1402
|
+
|
|
1402
1403
|
# ADR-26 — whether `class_name` is declared "open" by a
|
|
1403
1404
|
# loaded plugin (manifest `open_receivers:`). An open
|
|
1404
1405
|
# class responds beyond its RBS surface, so the
|
|
@@ -1407,11 +1408,14 @@ module Rigor
|
|
|
1407
1408
|
# RBS method table, so proving a call "undefined" against it is
|
|
1408
1409
|
# unsound: a plugin-declared open receiver, a Rigor-bundled-
|
|
1409
1410
|
# overlay open receiver ({GEM_OVERLAY_OPEN_RECEIVERS}) whose
|
|
1410
|
-
# bundled directory this run actually loaded,
|
|
1411
|
+
# bundled directory this run actually loaded, a core-overlay
|
|
1412
|
+
# open receiver ({CORE_OVERLAY_OPEN_RECEIVERS}), or a
|
|
1411
1413
|
# Rigor-synthesized stub type (see `RbsLoader#synthesized_type_names`).
|
|
1412
1414
|
def unbounded_receiver_surface?(class_name, scope)
|
|
1415
|
+
bare_name = class_name.to_s.sub(/\A::/, "")
|
|
1413
1416
|
open_receiver?(class_name, scope) || synthesized_stub_receiver?(class_name, scope) ||
|
|
1414
|
-
|
|
1417
|
+
CORE_OVERLAY_OPEN_RECEIVERS.include?(bare_name) ||
|
|
1418
|
+
(GEM_OVERLAY_OPEN_RECEIVERS.include?(bare_name) && gem_overlay_loaded?(scope))
|
|
1415
1419
|
end
|
|
1416
1420
|
|
|
1417
1421
|
def open_receiver?(class_name, scope)
|
|
@@ -2093,7 +2097,13 @@ module Rigor
|
|
|
2093
2097
|
# arm restores the polarity without touching the shared helper:
|
|
2094
2098
|
# `String | nil` keeps firing, and so does `Dynamic | String | nil` —
|
|
2095
2099
|
# the nameless arm stays permissive inside the all-arms check, which is
|
|
2096
|
-
# only about the arms' method surface.
|
|
2100
|
+
# only about the arms' method surface. That union no longer arises from
|
|
2101
|
+
# a merge re-joining the narrowed edges of one untyped local, which
|
|
2102
|
+
# binds it back to `Dynamic[top]` (issue #1840,
|
|
2103
|
+
# `Inference::UntypedRejoin`); it still does when the program writes
|
|
2104
|
+
# the `nil` (`x = nil if c`) and no `nil?` edge strips it before the
|
|
2105
|
+
# re-joining merge, or when the arms are not a narrowing of one
|
|
2106
|
+
# untyped value.
|
|
2097
2107
|
def union_has_nameable_non_nil_arm?(union, scope)
|
|
2098
2108
|
union.members.any? do |m|
|
|
2099
2109
|
next false if nil_member?(m)
|
|
@@ -2195,7 +2205,8 @@ module Rigor
|
|
|
2195
2205
|
# ADR-121 WD7 — a refinement in effect (or one Rigor cannot read) may define the name on one arm's class.
|
|
2196
2206
|
def refined_on_some_arm?(members, call_node, scope, lexical_sites)
|
|
2197
2207
|
members.any? do |member|
|
|
2198
|
-
|
|
2208
|
+
kind = member.is_a?(Type::Singleton) ? :singleton : :instance
|
|
2209
|
+
refined_method_in_effect?(concrete_class_name(member), call_node, scope, kind, lexical_sites)
|
|
2199
2210
|
end
|
|
2200
2211
|
end
|
|
2201
2212
|
|
|
@@ -80,7 +80,7 @@ module Rigor
|
|
|
80
80
|
private
|
|
81
81
|
|
|
82
82
|
def effects_descriptor(paths)
|
|
83
|
-
files = PathExpansion.ruby_files(paths, @configuration.exclude_patterns)
|
|
83
|
+
files = PathExpansion.ruby_files(paths, @configuration.exclude_patterns, base: @configuration.exclude_base)
|
|
84
84
|
base = RunCacheKey.descriptor(
|
|
85
85
|
configuration: @configuration, files: files, explain: false,
|
|
86
86
|
rbs_config_entries: RunCacheKey.libraries_config_entries(@configuration)
|
|
@@ -120,6 +120,18 @@ module Rigor
|
|
|
120
120
|
appeared.freeze
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
+
# Issue #1459 — the method names some file in `paths` started or stopped defining in this edit, on any class or
|
|
124
|
+
# side. A consumer whose answer asked whether the program defines a name anywhere
|
|
125
|
+
# (`Inference::BlockCallTiming.project_defines_anywhere?`) recorded a `defines:<name>` edge whichever way it
|
|
126
|
+
# answered. `paths` MUST include removed files, whose every name vanished. `before` / `after` map a path to the
|
|
127
|
+
# Set of names it defines (`Inference::ScopeIndexer.defined_method_names`); a path absent from either side
|
|
128
|
+
# defines nothing there.
|
|
129
|
+
def changed_defined_names(paths, before, after)
|
|
130
|
+
moved = Set.new
|
|
131
|
+
paths.each { |path| moved.merge((before[path] || Set.new) ^ (after[path] || Set.new)) }
|
|
132
|
+
moved.freeze
|
|
133
|
+
end
|
|
134
|
+
|
|
123
135
|
# Issue #1120 — the method names whose refinements moved in this edit. A consumer whose answer consulted the
|
|
124
136
|
# refinement table recorded a `refinement:<method>` edge whichever way it answered, because the answer is a
|
|
125
137
|
# function of the whole project's refinements of that name. `paths` MUST include removed files, whose whole
|
|
@@ -42,8 +42,8 @@ module Rigor
|
|
|
42
42
|
# plugin's identity and the engine's (key slots), so a project whose plugins claim template globs is served
|
|
43
43
|
# too. The ADR-87 probe misses on one: the plain slot's key carries the `template-units` slot.
|
|
44
44
|
#
|
|
45
|
-
# `explain:` is always false: the session
|
|
46
|
-
# `--explain`.
|
|
45
|
+
# `explain:` is always false: the session writes no slot for an `--explain` run (#1533), and the CLI declines
|
|
46
|
+
# the probe under `--explain`.
|
|
47
47
|
module IncrementalRunSlot
|
|
48
48
|
module_function
|
|
49
49
|
|
|
@@ -115,7 +115,7 @@ module Rigor
|
|
|
115
115
|
def serve(configuration:, cache_root:, paths:)
|
|
116
116
|
return nil if configuration.effects_enabled?
|
|
117
117
|
|
|
118
|
-
files = PathExpansion.ruby_files(paths, configuration.exclude_patterns)
|
|
118
|
+
files = PathExpansion.ruby_files(paths, configuration.exclude_patterns, base: configuration.exclude_base)
|
|
119
119
|
slot_key = key(configuration: configuration, target: Target.new(files: files, roots: paths))
|
|
120
120
|
return nil if slot_key.nil?
|
|
121
121
|
|