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
|
@@ -20,10 +20,18 @@ module Rigor
|
|
|
20
20
|
#
|
|
21
21
|
# Issue #1740 — {OwnMethod} shares {.levels} with the walk below and asks the opposite question for the two
|
|
22
22
|
# argument checks: does the receiver's own method answer ahead of every refinement in effect?
|
|
23
|
+
#
|
|
24
|
+
# Issue #1752 — a class object is walked through its own lookup ({ClassObject}): its singleton class and each
|
|
25
|
+
# superclass's, then `Class`, `Module`, `Object`, `Kernel` and `BasicObject`, so a refinement of `Object` answers
|
|
26
|
+
# `Integer.oo` while `Integer.sqrt` keeps answering ahead of one.
|
|
23
27
|
module RefinedDispatch
|
|
24
28
|
# The refinement that answers: `module_name` refines `refined_class` (a class or module on the walk).
|
|
25
29
|
Winner = Data.define(:module_name, :refined_class)
|
|
26
30
|
|
|
31
|
+
# Issue #1752 — the singleton class `#<Class:of>` as an entry of a class object's walk ({ClassObject}): it
|
|
32
|
+
# defines the name through `of`'s own singleton methods, and no refinement table key names it.
|
|
33
|
+
SingletonClass = Data.define(:of)
|
|
34
|
+
|
|
27
35
|
# The answer when the in-effect list carries {InEffectRefinements::UNKNOWN} and some refinement defines the
|
|
28
36
|
# name on a class the walk reaches: any of them may answer.
|
|
29
37
|
UNKNOWN = :unknown
|
|
@@ -37,14 +45,16 @@ module Rigor
|
|
|
37
45
|
def wildcard = Scope::DiscoveryIndex::REFINEMENT_WILDCARD
|
|
38
46
|
|
|
39
47
|
# A {Winner}, {UNKNOWN}, or nil when no in-effect refinement answers `method_name` on an instance of
|
|
40
|
-
# `class_name` (the class's own lookup does)
|
|
48
|
+
# `class_name` (the class's own lookup does), or with `kind: :singleton` on the class object `class_name`
|
|
49
|
+
# itself (issue #1752). `list` is the call site's in-effect refinements.
|
|
41
50
|
#
|
|
42
51
|
# ADR-121 WD7 — a row the walk could not read never yields a {Winner}: an in-effect module's row for the name
|
|
43
52
|
# whose class the walk could not name reaches every receiver, and a names-wildcard row of a level the walk
|
|
44
53
|
# reaches before a definer may define the name, so both answer {UNKNOWN}. So does a winner whose module shares
|
|
45
54
|
# its last segment with another listed module (A1): which of a `using`'s declared candidates Ruby finds can
|
|
46
|
-
# depend on load order.
|
|
47
|
-
|
|
55
|
+
# depend on load order. A class object's walk always ends at `Object`, so where it cannot be read a row that
|
|
56
|
+
# may sit on it answers {UNKNOWN} too.
|
|
57
|
+
def winner(scope, class_name, method_name, list, kind: :instance)
|
|
48
58
|
refinements = scope.discovered_refinements
|
|
49
59
|
return UNKNOWN if class_unknown_row?(scope, refinements, method_name, list)
|
|
50
60
|
|
|
@@ -53,13 +63,26 @@ module Rigor
|
|
|
53
63
|
unknown = list.include?(InEffectRefinements::UNKNOWN)
|
|
54
64
|
return nil if targets.nil? && unread.nil? && !unknown
|
|
55
65
|
|
|
56
|
-
levels = levels(scope, class_name)
|
|
66
|
+
levels = levels(scope, class_name, kind: kind)
|
|
67
|
+
return unread_class_object(scope, refinements, method_name, list) if levels.nil? && kind == :singleton
|
|
57
68
|
return nil if levels.nil?
|
|
58
69
|
return UNKNOWN if unknown && unknown_reaches?(scope, refinements, method_name, levels)
|
|
59
70
|
|
|
60
71
|
walk_targets(scope, levels, method_name, [targets, unread], list)
|
|
61
72
|
end
|
|
62
73
|
|
|
74
|
+
# {UNKNOWN} when some row for `method_name` (or a names-wildcard row) that `list` puts in effect, or any row
|
|
75
|
+
# under the unknown marker, names a class or module a class object may reach ({ClassObject.reachable?}).
|
|
76
|
+
def unread_class_object(scope, refinements, method_name, list)
|
|
77
|
+
unknown = list.include?(InEffectRefinements::UNKNOWN)
|
|
78
|
+
reaches = refinements.any? do |refined, methods|
|
|
79
|
+
rows = [methods[method_name], methods[wildcard]].compact
|
|
80
|
+
!rows.empty? && (unknown || rows.any? { |modules| in_effect?(modules, list) }) &&
|
|
81
|
+
ClassObject.reachable?(scope, refined)
|
|
82
|
+
end
|
|
83
|
+
reaches ? UNKNOWN : nil
|
|
84
|
+
end
|
|
85
|
+
|
|
63
86
|
def walk_targets(scope, levels, method_name, (targets, unread), list)
|
|
64
87
|
return nil if targets.nil? && unread.nil?
|
|
65
88
|
|
|
@@ -165,20 +188,40 @@ module Rigor
|
|
|
165
188
|
# refinement first, as any level is; past that, the mixin may define the name, so a refinement further up the
|
|
166
189
|
# walk may or may not win and the answer is {UNKNOWN}, never the replaced method's. ADR-121 WD7: a level an
|
|
167
190
|
# in-effect module has a names-wildcard row for (`unread`) may define the name too, so it answers {UNKNOWN}.
|
|
191
|
+
#
|
|
192
|
+
# Issue #1752 — on a class object's walk a singleton level that only an entry no table names may answer
|
|
193
|
+
# (`:continue_unsure`) makes a refinement reached past it {UNKNOWN}: that entry may define the name.
|
|
168
194
|
def walk(scope, levels, method_name, targets, unread)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return targeted_after?(levels, index, targets, unread) ? UNKNOWN : nil if mixed
|
|
195
|
+
unsure = false
|
|
196
|
+
levels.each_index do |index|
|
|
197
|
+
answer = level_answer(scope, levels, index, method_name, [targets, unread])
|
|
198
|
+
next unsure = true if answer == :continue_unsure
|
|
199
|
+
next if answer == :continue
|
|
175
200
|
|
|
176
|
-
|
|
177
|
-
return answer unless answer == :continue
|
|
201
|
+
return unsure && answer.is_a?(Winner) ? UNKNOWN : answer
|
|
178
202
|
end
|
|
179
203
|
nil
|
|
180
204
|
end
|
|
181
205
|
|
|
206
|
+
def level_answer(scope, levels, index, method_name, (targets, unread))
|
|
207
|
+
level_class, entries, mixed, every = levels[index]
|
|
208
|
+
return UNKNOWN if level_class && unread.key?(level_class)
|
|
209
|
+
|
|
210
|
+
refining = level_class && targets[level_class]
|
|
211
|
+
return Winner.new(module_name: refining, refined_class: level_class) if refining
|
|
212
|
+
return targeted_after?(levels, index, targets, unread) ? UNKNOWN : nil if mixed
|
|
213
|
+
|
|
214
|
+
walk_level(scope, [entries, every], level_class, method_name, [targets, unread])
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def walk_level(scope, (entries, every), level_class, method_name, (targets, unread))
|
|
218
|
+
if level_class.is_a?(SingletonClass)
|
|
219
|
+
return ClassObject.walk_level(scope, every || entries, method_name, targets, unread)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
walk_entries(scope, entries, level_class, method_name, targets, unread)
|
|
223
|
+
end
|
|
224
|
+
|
|
182
225
|
# A level's prepended modules, its own method and its included modules, in that order: `:continue` when none
|
|
183
226
|
# answers, so the walk moves on.
|
|
184
227
|
def walk_entries(scope, entries, level_class, method_name, targets, unread)
|
|
@@ -205,7 +248,10 @@ module Rigor
|
|
|
205
248
|
# name. `every entry` (project levels only) is the level's entries in lookup order with each one that
|
|
206
249
|
# neither the project's resolver nor RBS can name kept as its candidate names (an Array) where `entries`
|
|
207
250
|
# leaves it out: the typer never reads it, and {OwnMethod} declines where a refinement may name one.
|
|
208
|
-
|
|
251
|
+
# `kind: :singleton` answers the class object `class_name` instead ({ClassObject.levels}, issue #1752).
|
|
252
|
+
def levels(scope, class_name, kind: :instance)
|
|
253
|
+
return ClassObject.levels(scope, class_name) if kind == :singleton
|
|
254
|
+
|
|
209
255
|
environment = scope.environment
|
|
210
256
|
return rbs_levels(scope, class_name) if environment&.class_known?(class_name)
|
|
211
257
|
|
|
@@ -257,8 +303,9 @@ module Rigor
|
|
|
257
303
|
|
|
258
304
|
# Does `name` define `method_name` on its instance side itself: a project `def` (its dependency edge recorded
|
|
259
305
|
# through `Scope#user_def_for`), another project definer (`attr_*`, `define_method`), or an RBS declaration
|
|
260
|
-
# written on `name` rather than on an ancestor?
|
|
306
|
+
# written on `name` rather than on an ancestor? A {SingletonClass} asks its own singleton methods.
|
|
261
307
|
def defines?(scope, name, method_name)
|
|
308
|
+
return ClassObject.defines?(scope, name.of, method_name) if name.is_a?(SingletonClass)
|
|
262
309
|
return true if scope.user_def_for(name, method_name)
|
|
263
310
|
return true if scope.discovered_method?(name, method_name, :instance)
|
|
264
311
|
|
|
@@ -269,4 +316,5 @@ module Rigor
|
|
|
269
316
|
end
|
|
270
317
|
end
|
|
271
318
|
|
|
319
|
+
require_relative "refined_dispatch/class_object"
|
|
272
320
|
require_relative "refined_dispatch/own_method"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../type"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
# Issue #1889 — `instance_variable_set` called on `self` writes an instance variable that no write node shows.
|
|
10
|
+
# Left unseen, the binding the call site holds outlived it: `@n = 0; instance_variable_set(:@n, 5)` kept `@n`
|
|
11
|
+
# on `0`, and a block that wrote `@n` only this way never entered the captured-binding name set, so
|
|
12
|
+
# `[1, 2].inject(0) { |a, e| instance_variable_set(:@n, @n + 1); a + @n }` folded to a constant.
|
|
13
|
+
#
|
|
14
|
+
# A call counts when its receiver is implicit or `self` and it passes any argument ({.self_write?}). With
|
|
15
|
+
# exactly two plain positional arguments, the first a Symbol or String literal naming an instance variable
|
|
16
|
+
# (`:@n`, `"@n"`; Ruby accepts both), it is a write of that variable ({.literal_name}). Any other argument list
|
|
17
|
+
# — a computed name, a splat (`instance_variable_set(*args)`), a keyword hash, or a literal Ruby rejects with
|
|
18
|
+
# `NameError` (`:n`) — names an ivar the analysis cannot see, so every ivar the scope binds may have been
|
|
19
|
+
# written ({.apply}; for a rejected literal this over-approximates, which only widens).
|
|
20
|
+
# `obj.instance_variable_set` writes another object's ivar and does not count; neither does the reflective
|
|
21
|
+
# `send(:instance_variable_set, …)`.
|
|
22
|
+
module ReflectiveIvarWrite
|
|
23
|
+
METHOD_NAME = :instance_variable_set
|
|
24
|
+
private_constant :METHOD_NAME
|
|
25
|
+
|
|
26
|
+
IVAR_NAME = /\A@[A-Za-z_]\w*\z/
|
|
27
|
+
private_constant :IVAR_NAME
|
|
28
|
+
|
|
29
|
+
REPEATING_KEY = :__rigor_reflective_ivar_write_repeating__
|
|
30
|
+
private_constant :REPEATING_KEY
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# True when `node` is an `instance_variable_set` on `self` with at least one argument.
|
|
35
|
+
def self_write?(node)
|
|
36
|
+
return false unless node.is_a?(Prism::CallNode) && node.name == METHOD_NAME
|
|
37
|
+
return false unless node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
|
|
38
|
+
|
|
39
|
+
arguments = node.arguments&.arguments
|
|
40
|
+
!arguments.nil? && !arguments.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# `:@n` for `instance_variable_set(:@n, v)` or `instance_variable_set("@n", v)` on `self`, else nil.
|
|
44
|
+
def literal_name(node)
|
|
45
|
+
return nil unless self_write?(node)
|
|
46
|
+
|
|
47
|
+
arguments = node.arguments.arguments
|
|
48
|
+
return nil unless arguments.size == 2 && arguments.none? { |arg| splat_or_keywords?(arg) }
|
|
49
|
+
|
|
50
|
+
name = arguments.first
|
|
51
|
+
text =
|
|
52
|
+
case name
|
|
53
|
+
when Prism::SymbolNode, Prism::StringNode then name.unescaped
|
|
54
|
+
end
|
|
55
|
+
text && IVAR_NAME.match?(text) ? text.to_sym : nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# True for a call {.self_write?} takes whose target {.literal_name} cannot name: it may write any ivar.
|
|
59
|
+
def unknown_target?(node)
|
|
60
|
+
self_write?(node) && literal_name(node).nil?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The value argument of a call {.literal_name} names.
|
|
64
|
+
def value_node(node)
|
|
65
|
+
node.arguments.arguments[1]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Runs the block with the evaluator inside a `while` / `until` / `for` body. The loop's rebind fixpoint widens
|
|
69
|
+
# locals only, so a literal-name write there must not replace the binding with one pass's value
|
|
70
|
+
# ({.apply}).
|
|
71
|
+
def in_repeating_body
|
|
72
|
+
depth = Thread.current[REPEATING_KEY] || 0
|
|
73
|
+
Thread.current[REPEATING_KEY] = depth + 1
|
|
74
|
+
begin
|
|
75
|
+
yield
|
|
76
|
+
ensure
|
|
77
|
+
Thread.current[REPEATING_KEY] = depth
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def repeating_body?
|
|
82
|
+
(Thread.current[REPEATING_KEY] || 0).positive?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# `scope` after `node` runs. A literal name binds that ivar to the value's type (yielded), as `@n = v` does.
|
|
86
|
+
# Inside a loop body ({.in_repeating_body}) the value and `Dynamic[top]` are joined into the current binding
|
|
87
|
+
# instead: the loop's fixpoint widens locals only, so its single pass over an ivar types the value from one
|
|
88
|
+
# iteration's locals (`instance_variable_set(:@n, i + 1)` stored `1`) and would leave that. Any other call joins
|
|
89
|
+
# `Dynamic[top]` into every ivar `scope` binds: any one of them may now hold the value, and joining the
|
|
90
|
+
# value's own type would report a method the old value has and the new one lacks. An ivar on its ADR-58
|
|
91
|
+
# declaration seed keeps its mark, which stops a nil in the seed from fuelling a nil-receiver diagnostic.
|
|
92
|
+
def apply(node, scope)
|
|
93
|
+
return scope unless self_write?(node)
|
|
94
|
+
|
|
95
|
+
name = literal_name(node)
|
|
96
|
+
return bind_literal(scope, name, yield) if name
|
|
97
|
+
|
|
98
|
+
floor = Type::Combinator.untyped
|
|
99
|
+
scope.ivars.reduce(scope) do |acc, (ivar, type)|
|
|
100
|
+
widened = Type::Combinator.union(type, floor)
|
|
101
|
+
rebound = acc.with_ivar(ivar, widened).without_ivar_guard(ivar)
|
|
102
|
+
scope.declaration_sourced?(:ivar, ivar) ? rebound.seed_declaration_sourced_ivar(ivar, widened) : rebound
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def bind_literal(scope, name, value)
|
|
107
|
+
type = repeating_body? ? Type::Combinator.union(*scope.ivar(name), value, Type::Combinator.untyped) : value
|
|
108
|
+
scope.with_ivar(name, type).without_ivar_guard(name)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def splat_or_keywords?(arg)
|
|
112
|
+
arg.is_a?(Prism::SplatNode) || arg.is_a?(Prism::KeywordHashNode) || arg.is_a?(Prism::BlockArgumentNode)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|