rigortype 0.1.5 → 0.1.7
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 +76 -79
- data/lib/rigor/analysis/baseline.rb +347 -0
- data/lib/rigor/analysis/buffer_binding.rb +36 -0
- data/lib/rigor/analysis/check_rules.rb +68 -3
- data/lib/rigor/analysis/dependency_source_inference/index.rb +14 -1
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +105 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +32 -12
- data/lib/rigor/analysis/project_scan.rb +39 -0
- data/lib/rigor/analysis/runner.rb +309 -22
- data/lib/rigor/analysis/worker_session.rb +14 -2
- data/lib/rigor/builtins/hkt_builtins.rb +342 -0
- data/lib/rigor/builtins/static_return_refinements.rb +142 -0
- data/lib/rigor/cache/store.rb +33 -3
- data/lib/rigor/cli/baseline_command.rb +377 -0
- data/lib/rigor/cli/lsp_command.rb +129 -0
- data/lib/rigor/cli/type_of_command.rb +44 -5
- data/lib/rigor/cli.rb +142 -13
- data/lib/rigor/configuration.rb +58 -2
- data/lib/rigor/environment/hkt_registry_holder.rb +33 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +67 -2
- data/lib/rigor/environment/reporters.rb +40 -0
- data/lib/rigor/environment.rb +119 -9
- data/lib/rigor/flow_contribution/fact.rb +20 -10
- data/lib/rigor/inference/acceptance.rb +48 -3
- data/lib/rigor/inference/expression_typer.rb +64 -2
- data/lib/rigor/inference/hkt_body.rb +171 -0
- data/lib/rigor/inference/hkt_body_parser.rb +363 -0
- data/lib/rigor/inference/hkt_reducer.rb +256 -0
- data/lib/rigor/inference/hkt_registry.rb +223 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +125 -30
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +32 -11
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +87 -0
- data/lib/rigor/inference/method_dispatcher.rb +174 -6
- data/lib/rigor/inference/narrowing.rb +103 -1
- data/lib/rigor/inference/project_patched_methods.rb +70 -0
- data/lib/rigor/inference/project_patched_scanner.rb +210 -0
- data/lib/rigor/inference/scope_indexer.rb +209 -19
- data/lib/rigor/inference/statement_evaluator.rb +172 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +94 -16
- data/lib/rigor/language_server/buffer_table.rb +63 -0
- data/lib/rigor/language_server/completion_provider.rb +438 -0
- data/lib/rigor/language_server/debouncer.rb +86 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +167 -0
- data/lib/rigor/language_server/document_symbol_provider.rb +142 -0
- data/lib/rigor/language_server/folding_range_provider.rb +75 -0
- data/lib/rigor/language_server/hover_provider.rb +74 -0
- data/lib/rigor/language_server/hover_renderer.rb +312 -0
- data/lib/rigor/language_server/loop.rb +71 -0
- data/lib/rigor/language_server/project_context.rb +145 -0
- data/lib/rigor/language_server/selection_range_provider.rb +93 -0
- data/lib/rigor/language_server/server.rb +384 -0
- data/lib/rigor/language_server/signature_help_provider.rb +249 -0
- data/lib/rigor/language_server/synchronized_writer.rb +28 -0
- data/lib/rigor/language_server/uri.rb +40 -0
- data/lib/rigor/language_server.rb +29 -0
- data/lib/rigor/plugin/base.rb +63 -0
- data/lib/rigor/plugin/macro/heredoc_template.rb +127 -13
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +54 -7
- data/lib/rigor/plugin/registry.rb +19 -0
- data/lib/rigor/rbs_extended/hkt_directives.rb +326 -0
- data/lib/rigor/rbs_extended.rb +82 -2
- data/lib/rigor/sig_gen/generator.rb +12 -3
- data/lib/rigor/type/app.rb +107 -0
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/environment.rbs +10 -4
- data/sig/rigor/inference.rbs +2 -0
- data/sig/rigor.rbs +4 -1
- metadata +56 -1
|
@@ -228,10 +228,26 @@ module Rigor
|
|
|
228
228
|
def ivar_mismatch_diagnostics_for(path, class_name, ivar_name, writes)
|
|
229
229
|
return [] if writes.size < 2
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
# Skip past leading `NilClass` writes when establishing
|
|
232
|
+
# the canonical type. The common nullable-slot idiom
|
|
233
|
+
# (`@x = nil` placeholder in `initialize` / a default
|
|
234
|
+
# state slot, then `@x = :foo` on first concrete state)
|
|
235
|
+
# would otherwise fire a false positive on every
|
|
236
|
+
# concrete write because `first_class` was `NilClass`
|
|
237
|
+
# and every subsequent `Symbol` / `String` / `Hash`
|
|
238
|
+
# write triggered the divergence rule. The first
|
|
239
|
+
# concrete (non-nil) write is the canonical type;
|
|
240
|
+
# additional `NilClass` writes are still tolerated
|
|
241
|
+
# downstream by the existing `other_class == "NilClass"`
|
|
242
|
+
# check (the nullable-slot resets to nil between work).
|
|
243
|
+
canonical = writes.find { |w| ivar_class_for(w[:type]) != "NilClass" }
|
|
244
|
+
return [] if canonical.nil?
|
|
245
|
+
|
|
246
|
+
first_class = ivar_class_for(canonical[:type])
|
|
232
247
|
return [] if first_class.nil?
|
|
233
248
|
|
|
234
|
-
writes
|
|
249
|
+
canonical_index = writes.index(canonical)
|
|
250
|
+
writes[(canonical_index + 1)..].filter_map do |write|
|
|
235
251
|
other_class = ivar_class_for(write[:type])
|
|
236
252
|
next nil if other_class.nil? || other_class == "NilClass" || other_class == first_class
|
|
237
253
|
|
|
@@ -358,9 +374,27 @@ module Rigor
|
|
|
358
374
|
method_def = lookup_method(receiver_type, class_name, call_node.name, scope)
|
|
359
375
|
return nil if method_def
|
|
360
376
|
|
|
377
|
+
# Module-mixin fallback (mirror of
|
|
378
|
+
# `MethodDispatcher#user_class_fallback_receiver`'s module
|
|
379
|
+
# path): an instance method on a module-mixin like
|
|
380
|
+
# `PP::ObjectMixin` observes Kernel / Object methods
|
|
381
|
+
# through every concrete includer's ancestor chain, so an
|
|
382
|
+
# unresolved `self.inspect` / `self.respond_to?` /
|
|
383
|
+
# `self.class` MUST NOT fire `undefined-method`. Retry
|
|
384
|
+
# against Object before the rule fires.
|
|
385
|
+
return nil if module_mixin_receiver?(receiver_type, scope) &&
|
|
386
|
+
lookup_method(receiver_type, "Object", call_node.name, scope)
|
|
387
|
+
|
|
361
388
|
build_undefined_method_diagnostic(path, call_node, receiver_type)
|
|
362
389
|
end
|
|
363
390
|
|
|
391
|
+
def module_mixin_receiver?(receiver_type, scope)
|
|
392
|
+
return false unless receiver_type.is_a?(Type::Nominal)
|
|
393
|
+
return false if scope.environment.nil?
|
|
394
|
+
|
|
395
|
+
scope.environment.rbs_module?(receiver_type.class_name)
|
|
396
|
+
end
|
|
397
|
+
|
|
364
398
|
# Returns a qualified class name for the in-scope check.
|
|
365
399
|
# Nominal / Singleton carry a single-class identity
|
|
366
400
|
# directly. Constant projects to its value's class.
|
|
@@ -944,8 +978,18 @@ module Rigor
|
|
|
944
978
|
# / Constant / Tuple / HashShape; the wrapper exists so
|
|
945
979
|
# the ivar rule can extend the envelope (or apply
|
|
946
980
|
# different filters) without disturbing the call rules.
|
|
981
|
+
#
|
|
982
|
+
# `TrueClass` / `FalseClass` are both normalised to
|
|
983
|
+
# `"bool"` here so the common boolean-flag idiom
|
|
984
|
+
# (`@loaded = false` in `initialize` then `@loaded = true`
|
|
985
|
+
# on first work) doesn't fire the mismatch rule. A real
|
|
986
|
+
# `bool → String` drift still trips because the second
|
|
987
|
+
# write's `ivar_class_for` returns `"String"`.
|
|
947
988
|
def ivar_class_for(type)
|
|
948
|
-
concrete_class_name(type)
|
|
989
|
+
name = concrete_class_name(type)
|
|
990
|
+
return "bool" if %w[TrueClass FalseClass].include?(name)
|
|
991
|
+
|
|
992
|
+
name
|
|
949
993
|
end
|
|
950
994
|
|
|
951
995
|
def build_always_truthy_condition_diagnostic(path, predicate_node, polarity)
|
|
@@ -1032,8 +1076,29 @@ module Rigor
|
|
|
1032
1076
|
# (no splat / kw / block-pass / forwarded).
|
|
1033
1077
|
# - Per-argument: skip when EITHER side is `Dynamic`
|
|
1034
1078
|
# (the call cannot be statically refuted).
|
|
1079
|
+
# Ruby's universal-equality methods accept any object
|
|
1080
|
+
# per the `Object#==(other) → bool` /
|
|
1081
|
+
# `Object#eql?(other) → bool` contract. Even when a
|
|
1082
|
+
# subclass overrides `==` to compare specific shapes
|
|
1083
|
+
# (URI::Generic#==(URI::Generic), Time#==(Time), …),
|
|
1084
|
+
# the runtime convention is to RETURN false for
|
|
1085
|
+
# type-mismatched arguments rather than raise. RBS sigs
|
|
1086
|
+
# that declare a tight parameter type therefore over-
|
|
1087
|
+
# specify; checking arguments against them produces
|
|
1088
|
+
# spurious mismatches such as
|
|
1089
|
+
# `URI::Generic#==(URI::Generic)`
|
|
1090
|
+
# called with `URI::HTTP | nil`
|
|
1091
|
+
# tdiary-core's `config_uri == referer_uri` (where
|
|
1092
|
+
# `referer_uri` is `URI.parse(...) if condition`, hence
|
|
1093
|
+
# union-with-nil) is the canonical case. Skip arg
|
|
1094
|
+
# checking on these methods entirely; the call is
|
|
1095
|
+
# well-formed by Ruby's contract.
|
|
1096
|
+
UNIVERSAL_EQUALITY_METHODS = %i[== != eql? equal? <=>].to_set.freeze
|
|
1097
|
+
private_constant :UNIVERSAL_EQUALITY_METHODS
|
|
1098
|
+
|
|
1035
1099
|
def argument_type_diagnostic(path, call_node, scope_index)
|
|
1036
1100
|
return nil if call_node.receiver.nil?
|
|
1101
|
+
return nil if UNIVERSAL_EQUALITY_METHODS.include?(call_node.name)
|
|
1037
1102
|
return nil unless plain_positional_call?(call_node)
|
|
1038
1103
|
|
|
1039
1104
|
scope = scope_index[call_node]
|
|
@@ -54,7 +54,7 @@ module Rigor
|
|
|
54
54
|
)
|
|
55
55
|
@resolved_gems = resolved_gems.freeze
|
|
56
56
|
@unresolvable = unresolvable.freeze
|
|
57
|
-
@method_catalog = method_catalog.freeze
|
|
57
|
+
@method_catalog = normalize_catalog(method_catalog).freeze
|
|
58
58
|
@budget_exceeded = budget_exceeded.freeze
|
|
59
59
|
@class_to_gem = class_to_gem.freeze
|
|
60
60
|
@budget_overrun_strategy = budget_overrun_strategy
|
|
@@ -62,6 +62,19 @@ module Rigor
|
|
|
62
62
|
freeze
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# Accepts both the post-heuristic `CatalogEntry` shape
|
|
66
|
+
# (the Walker's current output) and the legacy bare-Symbol
|
|
67
|
+
# `:instance` / `:singleton` shape (older tests + earlier
|
|
68
|
+
# in-tree callers). Symbol values are wrapped into
|
|
69
|
+
# `CatalogEntry(kind: value, return_type: nil)` so the
|
|
70
|
+
# downstream dispatcher always sees a single shape.
|
|
71
|
+
def normalize_catalog(catalog)
|
|
72
|
+
catalog.transform_values do |value|
|
|
73
|
+
value.is_a?(Symbol) ? Walker::CatalogEntry.new(kind: value) : value
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
private :normalize_catalog
|
|
77
|
+
|
|
65
78
|
# @return [String, nil] the gem that owns `class_name`
|
|
66
79
|
# (first-write-wins); `nil` when the class isn't in
|
|
67
80
|
# any opt-in gem's catalog.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../type"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Analysis
|
|
9
|
+
module DependencySourceInference
|
|
10
|
+
# Walker enhancement (ADR-10 § "Open questions"): pulls a
|
|
11
|
+
# **heuristic** return type out of a method body's tail
|
|
12
|
+
# expression. The heuristic is intentionally narrow — only
|
|
13
|
+
# the trivially-decidable shapes contribute. Everything
|
|
14
|
+
# else returns `nil`, which the dispatcher consumes as
|
|
15
|
+
# "fall back to `Dynamic[top]`" (the pre-enhancement
|
|
16
|
+
# behaviour).
|
|
17
|
+
#
|
|
18
|
+
# The contract is a strict floor:
|
|
19
|
+
#
|
|
20
|
+
# - Last statement is a literal scalar (Integer / Float /
|
|
21
|
+
# Symbol / true / false / nil) → `Constant<value>`.
|
|
22
|
+
# - Last statement is a String literal → `Nominal[String]`
|
|
23
|
+
# (NOT `Constant<"x">`; a String literal is mutable under
|
|
24
|
+
# `# frozen_string_literal: false`, so the analyzer cannot
|
|
25
|
+
# claim object identity).
|
|
26
|
+
# - Last statement is an Array / Hash literal →
|
|
27
|
+
# `Nominal[Array]` / `Nominal[Hash]` (element-type
|
|
28
|
+
# inference stays deferred — too expensive for the
|
|
29
|
+
# heuristic tier).
|
|
30
|
+
# - Last statement is `self` → `nil` (the caller's receiver
|
|
31
|
+
# nominal would be more accurate but the walker doesn't
|
|
32
|
+
# carry the receiver context; deferred).
|
|
33
|
+
# - Anything else → `nil`.
|
|
34
|
+
#
|
|
35
|
+
# The dispatcher wraps the returned type in `Dynamic[T]`
|
|
36
|
+
# before returning to the user per ADR-10's `Dynamic`-origin
|
|
37
|
+
# contract. The wrapping is the dispatcher's responsibility,
|
|
38
|
+
# not the heuristic's.
|
|
39
|
+
module ReturnTypeHeuristic
|
|
40
|
+
module_function
|
|
41
|
+
|
|
42
|
+
# @param def_node [Prism::DefNode]
|
|
43
|
+
# @return [Rigor::Type, nil] heuristic return type, or
|
|
44
|
+
# `nil` when the body's tail expression doesn't match
|
|
45
|
+
# any of the recognised shapes.
|
|
46
|
+
def extract(def_node)
|
|
47
|
+
body = def_node.body
|
|
48
|
+
return nil if body.nil?
|
|
49
|
+
|
|
50
|
+
tail = tail_expression(body)
|
|
51
|
+
literal_return_type(tail)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Extracts the last evaluated expression of the method
|
|
55
|
+
# body. A `Prism::DefNode`'s body is either a
|
|
56
|
+
# `StatementsNode` (multi-statement body) or a single
|
|
57
|
+
# expression node directly. We dig past `BeginNode` /
|
|
58
|
+
# rescue wrappers to the protected body's tail.
|
|
59
|
+
def tail_expression(node)
|
|
60
|
+
case node
|
|
61
|
+
when Prism::StatementsNode
|
|
62
|
+
tail_expression(node.body.last) unless node.body.empty?
|
|
63
|
+
when Prism::BeginNode
|
|
64
|
+
tail_expression(node.statements) if node.statements
|
|
65
|
+
when nil
|
|
66
|
+
nil
|
|
67
|
+
else
|
|
68
|
+
node
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
private_class_method :tail_expression
|
|
72
|
+
|
|
73
|
+
# The per-shape heuristic. Per the module docstring,
|
|
74
|
+
# immutable scalar literals fold to `Constant<value>`;
|
|
75
|
+
# mutable container literals (String, Array, Hash) fold
|
|
76
|
+
# to the appropriate Nominal; everything else returns
|
|
77
|
+
# nil.
|
|
78
|
+
def literal_return_type(node)
|
|
79
|
+
case node
|
|
80
|
+
when Prism::IntegerNode, Prism::FloatNode then Type::Combinator.constant_of(node.value)
|
|
81
|
+
when Prism::SymbolNode then symbol_constant(node)
|
|
82
|
+
when Prism::TrueNode then Type::Combinator.constant_of(true)
|
|
83
|
+
when Prism::FalseNode then Type::Combinator.constant_of(false)
|
|
84
|
+
when Prism::NilNode then Type::Combinator.constant_of(nil)
|
|
85
|
+
when Prism::StringNode then Type::Combinator.nominal_of("String")
|
|
86
|
+
when Prism::ArrayNode then Type::Combinator.nominal_of("Array")
|
|
87
|
+
when Prism::HashNode then Type::Combinator.nominal_of("Hash")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
private_class_method :literal_return_type
|
|
91
|
+
|
|
92
|
+
# `Prism::SymbolNode#value` returns the Symbol's name as
|
|
93
|
+
# a String. We `.to_sym` it for the Constant carrier so
|
|
94
|
+
# `:foo` is `Constant<:foo>`, not `Constant<"foo">`.
|
|
95
|
+
def symbol_constant(node)
|
|
96
|
+
value = node.value
|
|
97
|
+
return nil if value.nil?
|
|
98
|
+
|
|
99
|
+
Type::Combinator.constant_of(value.to_sym)
|
|
100
|
+
end
|
|
101
|
+
private_class_method :symbol_constant
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -2,23 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
+
require_relative "return_type_heuristic"
|
|
6
|
+
|
|
5
7
|
module Rigor
|
|
6
8
|
module Analysis
|
|
7
9
|
module DependencySourceInference
|
|
8
10
|
# Walks a resolved gem's `roots:` and collects the
|
|
9
|
-
# `(class_name, method_name) →
|
|
10
|
-
# method catalog. The walker is the source
|
|
11
|
-
# dispatcher tier
|
|
11
|
+
# `(class_name, method_name) → CatalogEntry(kind,
|
|
12
|
+
# return_type)` method catalog. The walker is the source
|
|
13
|
+
# of facts the dispatcher tier consults to recognise a
|
|
12
14
|
# method as defined by an opt-in gem and contribute a
|
|
13
|
-
# `Type::Dynamic
|
|
15
|
+
# `Type::Dynamic`-wrapped return at the call site.
|
|
14
16
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
17
|
+
# The dispatcher tier wraps every walker-contributed return
|
|
18
|
+
# in `Dynamic[T]` per ADR-10's gem-boundary contract. When
|
|
19
|
+
# the heuristic ({ReturnTypeHeuristic}) recognises the
|
|
20
|
+
# method body's tail expression, the dispatcher uses the
|
|
21
|
+
# heuristic's static facet; otherwise it falls back to
|
|
22
|
+
# `Dynamic[top]` (the pre-heuristic behaviour). The
|
|
23
|
+
# heuristic is intentionally narrow — only literal-tail
|
|
24
|
+
# method bodies fold; everything else degrades silently.
|
|
22
25
|
#
|
|
23
26
|
# Hard exclusions are NOT user-configurable, per ADR-10
|
|
24
27
|
# § "Hard exclusions": top-level `spec/`, `test/`, `bin/`,
|
|
@@ -45,6 +48,19 @@ module Rigor
|
|
|
45
48
|
def truncated? = truncated
|
|
46
49
|
end
|
|
47
50
|
|
|
51
|
+
# Per-method catalog entry. `kind` is `:instance` or
|
|
52
|
+
# `:singleton`; `return_type` is the
|
|
53
|
+
# {ReturnTypeHeuristic}-extracted static facet (a
|
|
54
|
+
# `Rigor::Type::*`) or `nil` when the heuristic declined.
|
|
55
|
+
# The dispatcher wraps a non-nil `return_type` in
|
|
56
|
+
# `Dynamic[T]`; a `nil` `return_type` falls back to
|
|
57
|
+
# `Dynamic[top]`.
|
|
58
|
+
class CatalogEntry < Data.define(:kind, :return_type)
|
|
59
|
+
def initialize(kind:, return_type: nil)
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
48
64
|
# Sentinel for "no cap" — used by callers that don't
|
|
49
65
|
# care about the budget (specs, tooling). Production
|
|
50
66
|
# code MUST pass an integer.
|
|
@@ -175,7 +191,11 @@ module Rigor
|
|
|
175
191
|
|
|
176
192
|
class_name = qualified_prefix.join("::")
|
|
177
193
|
kind = node.receiver.is_a?(Prism::SelfNode) || in_singleton_class ? :singleton : :instance
|
|
178
|
-
|
|
194
|
+
key = [class_name, node.name]
|
|
195
|
+
return if accumulator.key?(key) # first walk wins
|
|
196
|
+
|
|
197
|
+
return_type = ReturnTypeHeuristic.extract(node)
|
|
198
|
+
accumulator[key] = CatalogEntry.new(kind: kind, return_type: return_type)
|
|
179
199
|
end
|
|
180
200
|
|
|
181
201
|
# Resolves a `Prism::ConstantPathNode` /
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Analysis
|
|
5
|
+
# Frozen snapshot of the project-wide state {Runner} consumes
|
|
6
|
+
# before per-file analysis fires: the loaded plugin registry
|
|
7
|
+
# (with `#prepare` already invoked), the dependency-source
|
|
8
|
+
# index, the synthetic-method and project-patched indexes
|
|
9
|
+
# produced by the pre-pass scanners, and the diagnostics
|
|
10
|
+
# those passes emitted.
|
|
11
|
+
#
|
|
12
|
+
# Owners (`Rigor::LanguageServer::ProjectContext`, future
|
|
13
|
+
# editor / sig-gen integrations) build a ProjectScan once
|
|
14
|
+
# per project-state generation via
|
|
15
|
+
# `Runner#prepare_project_scan` and pass it to
|
|
16
|
+
# `Runner.new(prebuilt: ...)` so per-buffer publishes skip
|
|
17
|
+
# the scanner walks and `#prepare` re-runs. When watched
|
|
18
|
+
# project files change, the owner discards the ProjectScan
|
|
19
|
+
# and a fresh one builds on next read.
|
|
20
|
+
#
|
|
21
|
+
# Editor mode v1 contract reminder: scanners observe the
|
|
22
|
+
# bytes that were on disk at scan time, NOT the in-flight
|
|
23
|
+
# buffer. Edits to a file that itself declares synthetic
|
|
24
|
+
# methods (or `pre_eval:`-listed patches) are NOT visible
|
|
25
|
+
# until the owner invalidates the scan — typically via
|
|
26
|
+
# `workspace/didChangeWatchedFiles`. This is the same
|
|
27
|
+
# trade-off the LSP made when slice 7 cached only the
|
|
28
|
+
# `Environment`; extending the cache to the pre-pass
|
|
29
|
+
# outputs preserves the contract.
|
|
30
|
+
ProjectScan = Data.define(
|
|
31
|
+
:plugin_registry,
|
|
32
|
+
:dependency_source_index,
|
|
33
|
+
:synthetic_method_index,
|
|
34
|
+
:project_patched_methods,
|
|
35
|
+
:plugin_prepare_diagnostics,
|
|
36
|
+
:pre_eval_diagnostics
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|