rigortype 0.1.3 → 0.1.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 +125 -31
- data/lib/rigor/analysis/check_rules.rb +10 -18
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +47 -21
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +32 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference.rb +1 -0
- data/lib/rigor/analysis/diagnostic.rb +0 -2
- data/lib/rigor/analysis/fact_store.rb +11 -3
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/runner.rb +114 -3
- data/lib/rigor/builtins/imported_refinements.rb +360 -55
- data/lib/rigor/cache/descriptor.rb +1 -1
- data/lib/rigor/cache/store.rb +1 -1
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +173 -0
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_renderer.rb +1 -1
- data/lib/rigor/cli/type_scan_report.rb +2 -2
- data/lib/rigor/cli.rb +9 -1
- data/lib/rigor/configuration/dependencies.rb +2 -2
- data/lib/rigor/configuration.rb +2 -2
- data/lib/rigor/environment.rb +35 -4
- data/lib/rigor/flow_contribution/conflict.rb +2 -2
- data/lib/rigor/flow_contribution/element.rb +1 -1
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution/merge_result.rb +1 -1
- data/lib/rigor/flow_contribution/merger.rb +3 -3
- data/lib/rigor/flow_contribution.rb +2 -2
- data/lib/rigor/inference/block_parameter_binder.rb +0 -2
- data/lib/rigor/inference/coverage_scanner.rb +1 -1
- data/lib/rigor/inference/expression_typer.rb +67 -11
- data/lib/rigor/inference/fallback.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +0 -12
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +118 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +6 -11
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +0 -4
- data/lib/rigor/inference/method_dispatcher.rb +146 -2
- data/lib/rigor/inference/method_parameter_binder.rb +1 -3
- data/lib/rigor/inference/narrowing.rb +2 -4
- data/lib/rigor/inference/rbs_type_translator.rb +0 -2
- data/lib/rigor/inference/scope_indexer.rb +14 -9
- data/lib/rigor/inference/statement_evaluator.rb +7 -7
- data/lib/rigor/plugin/io_boundary.rb +0 -2
- data/lib/rigor/plugin/loader.rb +2 -2
- data/lib/rigor/plugin/manifest.rb +30 -9
- data/lib/rigor/plugin/registry.rb +11 -0
- data/lib/rigor/plugin/services.rb +1 -1
- data/lib/rigor/plugin/type_node_resolver.rb +52 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/reporter.rb +91 -0
- data/lib/rigor/rbs_extended.rb +131 -32
- data/lib/rigor/scope.rb +25 -8
- data/lib/rigor/sig_gen/classification.rb +36 -0
- data/lib/rigor/sig_gen/generator.rb +1048 -0
- data/lib/rigor/sig_gen/layout_index.rb +108 -0
- data/lib/rigor/sig_gen/method_candidate.rb +62 -0
- data/lib/rigor/sig_gen/observation_collector.rb +391 -0
- data/lib/rigor/sig_gen/observed_call.rb +62 -0
- data/lib/rigor/sig_gen/path_mapper.rb +116 -0
- data/lib/rigor/sig_gen/renderer.rb +157 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
- data/lib/rigor/sig_gen/write_result.rb +48 -0
- data/lib/rigor/sig_gen/writer.rb +530 -0
- data/lib/rigor/sig_gen.rb +25 -0
- data/lib/rigor/type/bound_method.rb +79 -0
- data/lib/rigor/type/combinator.rb +195 -2
- data/lib/rigor/type/constant.rb +13 -0
- data/lib/rigor/type/hash_shape.rb +0 -2
- data/lib/rigor/type/union.rb +20 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +62 -0
- data/lib/rigor/type_node/identifier.rb +30 -0
- data/lib/rigor/type_node/indexed_access.rb +41 -0
- data/lib/rigor/type_node/integer_literal.rb +29 -0
- data/lib/rigor/type_node/name_scope.rb +52 -0
- data/lib/rigor/type_node/resolver_chain.rb +56 -0
- data/lib/rigor/type_node/string_literal.rb +29 -0
- data/lib/rigor/type_node/symbol_literal.rb +28 -0
- data/lib/rigor/type_node/union.rb +42 -0
- data/lib/rigor/type_node.rb +29 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +2 -0
- data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
- data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
- data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
- data/sig/rigor/cli/diff_command.rbs +4 -0
- data/sig/rigor/cli/explain_command.rbs +4 -0
- data/sig/rigor/cli/sig_gen_command.rbs +4 -0
- data/sig/rigor/cli/type_scan_command.rbs +3 -0
- data/sig/rigor/environment.rbs +5 -2
- data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
- data/sig/rigor/inference/builtins.rbs +2 -0
- data/sig/rigor/plugin/access_denied_error.rbs +3 -0
- data/sig/rigor/plugin/base.rbs +6 -0
- data/sig/rigor/plugin/fact_store.rbs +11 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/load_error.rbs +6 -0
- data/sig/rigor/plugin/loader.rbs +20 -0
- data/sig/rigor/plugin/manifest.rbs +9 -0
- data/sig/rigor/plugin/registry.rbs +3 -0
- data/sig/rigor/plugin/services.rbs +3 -0
- data/sig/rigor/plugin/trust_policy.rbs +4 -0
- data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
- data/sig/rigor/plugin.rbs +8 -0
- data/sig/rigor/scope.rbs +4 -2
- data/sig/rigor/type.rbs +28 -6
- metadata +52 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module RbsExtended
|
|
5
|
+
# ADR-13 slice 3b — per-run accumulator for `RBS::Extended`
|
|
6
|
+
# diagnostic events that the parser / resolver cannot surface
|
|
7
|
+
# at the point of failure (the parsers are fail-soft, returning
|
|
8
|
+
# `nil` so call sites fall back to the RBS-declared type).
|
|
9
|
+
#
|
|
10
|
+
# Owns two event streams:
|
|
11
|
+
#
|
|
12
|
+
# - `#unresolved_payloads` — `rigor:v1:*` directive payloads
|
|
13
|
+
# the resolver could not turn into a {Rigor::Type}. Surface
|
|
14
|
+
# as `dynamic.rbs-extended.unresolved` `:info` diagnostics.
|
|
15
|
+
# - `#lossy_projections` — shape-projection type functions
|
|
16
|
+
# (`pick_of` / `omit_of` / `partial_of` / `required_of` /
|
|
17
|
+
# `readonly_of`) applied to a carrier that does not preserve
|
|
18
|
+
# shape information (anything other than `Type::HashShape`
|
|
19
|
+
# / `Type::Tuple`). Surface as
|
|
20
|
+
# `dynamic.shape.lossy-projection` `:info` diagnostics.
|
|
21
|
+
#
|
|
22
|
+
# Mutable through the run; consumed once by
|
|
23
|
+
# {Rigor::Analysis::Runner} at end-of-run. Each event is
|
|
24
|
+
# deduplicated by `(payload, source_location)` for unresolved
|
|
25
|
+
# and `(head, source_location)` for lossy-projection so a
|
|
26
|
+
# single annotation read from many call sites yields one
|
|
27
|
+
# diagnostic.
|
|
28
|
+
#
|
|
29
|
+
# The reporter is intentionally thread-safe via a coarse
|
|
30
|
+
# `Mutex` because the inference engine may read the same
|
|
31
|
+
# method definition from multiple files in parallel; the
|
|
32
|
+
# critical sections are short (Array#include? + Array#<<) so
|
|
33
|
+
# the lock contention is negligible.
|
|
34
|
+
class Reporter
|
|
35
|
+
UnresolvedEntry = Data.define(:payload, :source_location)
|
|
36
|
+
LossyProjectionEntry = Data.define(:head, :source_location)
|
|
37
|
+
|
|
38
|
+
def initialize
|
|
39
|
+
@unresolved_payloads = []
|
|
40
|
+
@lossy_projections = []
|
|
41
|
+
@mutex = Mutex.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Array<UnresolvedEntry>] frozen snapshot of the
|
|
45
|
+
# accumulated unresolved-payload events.
|
|
46
|
+
def unresolved_payloads
|
|
47
|
+
@mutex.synchronize { @unresolved_payloads.dup.freeze }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Array<LossyProjectionEntry>] frozen snapshot of
|
|
51
|
+
# the accumulated lossy-projection events.
|
|
52
|
+
def lossy_projections
|
|
53
|
+
@mutex.synchronize { @lossy_projections.dup.freeze }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Records a `dynamic.rbs-extended.unresolved` event. The
|
|
57
|
+
# `source_location` argument is the {RBS::Location} attached
|
|
58
|
+
# to the source annotation (or `nil` when the caller doesn't
|
|
59
|
+
# have one — the diagnostic falls back to a generic
|
|
60
|
+
# location in that case).
|
|
61
|
+
def record_unresolved(payload:, source_location: nil)
|
|
62
|
+
entry = UnresolvedEntry.new(payload: payload.to_s, source_location: source_location)
|
|
63
|
+
@mutex.synchronize do
|
|
64
|
+
return if @unresolved_payloads.include?(entry)
|
|
65
|
+
|
|
66
|
+
@unresolved_payloads << entry
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Records a `dynamic.shape.lossy-projection` event for one
|
|
71
|
+
# of the five shape-projection heads. `head` MUST be a
|
|
72
|
+
# String (`"pick_of"`, `"omit_of"`, …); the diagnostic
|
|
73
|
+
# message identifies which projection degraded.
|
|
74
|
+
def record_lossy_projection(head:, source_location: nil)
|
|
75
|
+
entry = LossyProjectionEntry.new(head: head.to_s, source_location: source_location)
|
|
76
|
+
@mutex.synchronize do
|
|
77
|
+
return if @lossy_projections.include?(entry)
|
|
78
|
+
|
|
79
|
+
@lossy_projections << entry
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# True when no events have accumulated. Used by callers
|
|
84
|
+
# that want to skip the diagnostic-emission pass entirely
|
|
85
|
+
# on the common no-event path.
|
|
86
|
+
def empty?
|
|
87
|
+
@mutex.synchronize { @unresolved_payloads.empty? && @lossy_projections.empty? }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/rigor/rbs_extended.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "type"
|
|
4
4
|
require_relative "builtins/imported_refinements"
|
|
5
5
|
require_relative "flow_contribution"
|
|
6
|
+
require_relative "rbs_extended/reporter"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
# Slice 7 phase 15 — first-preview reader for the
|
|
@@ -59,7 +60,7 @@ module Rigor
|
|
|
59
60
|
# current local type; `class_name` is then nil and
|
|
60
61
|
# `negative` is false (refinement-form directives do not
|
|
61
62
|
# support `~T` negation in v0.0.4).
|
|
62
|
-
PredicateEffect
|
|
63
|
+
class PredicateEffect < Data.define(:edge, :target_kind, :target_name, :class_name, :negative, :refinement_type)
|
|
63
64
|
def truthy_only? = edge == :truthy_only
|
|
64
65
|
def falsey_only? = edge == :falsey_only
|
|
65
66
|
def negative? = negative == true
|
|
@@ -98,7 +99,7 @@ module Rigor
|
|
|
98
99
|
#
|
|
99
100
|
# `negative` mirrors `PredicateEffect`: true when the
|
|
100
101
|
# directive uses `~ClassName` syntax.
|
|
101
|
-
AssertEffect
|
|
102
|
+
class AssertEffect < Data.define(:condition, :target_kind, :target_name, :class_name, :negative, :refinement_type)
|
|
102
103
|
def always? = condition == :always
|
|
103
104
|
def if_truthy_return? = condition == :if_truthy_return
|
|
104
105
|
def if_falsey_return? = condition == :if_falsey_return
|
|
@@ -129,15 +130,30 @@ module Rigor
|
|
|
129
130
|
# `rigor:v1:` directives are dropped. Returns an empty
|
|
130
131
|
# array (NEVER `nil`) for a method with no recognised
|
|
131
132
|
# annotations so callers can iterate unconditionally.
|
|
132
|
-
|
|
133
|
+
#
|
|
134
|
+
# @param environment [Rigor::Environment, nil] ADR-13 slice
|
|
135
|
+
# 3b. When provided, threads the plugin-supplied
|
|
136
|
+
# `name_scope:` and the per-run reporter through the
|
|
137
|
+
# annotation-parse path. `nil` (default) preserves the
|
|
138
|
+
# pre-slice-3b behaviour — no plugin resolvers consulted
|
|
139
|
+
# and no diagnostics accumulated.
|
|
140
|
+
def read_predicate_effects(method_def, environment: nil)
|
|
133
141
|
return [] if method_def.nil?
|
|
134
142
|
|
|
135
143
|
annotations = method_def.annotations
|
|
136
144
|
return [] if annotations.nil? || annotations.empty?
|
|
137
145
|
|
|
146
|
+
name_scope = environment&.name_scope
|
|
147
|
+
reporter = environment&.rbs_extended_reporter
|
|
148
|
+
|
|
138
149
|
effects = []
|
|
139
150
|
annotations.each do |annotation|
|
|
140
|
-
effect = parse_predicate_annotation(
|
|
151
|
+
effect = parse_predicate_annotation(
|
|
152
|
+
annotation.string,
|
|
153
|
+
name_scope: name_scope,
|
|
154
|
+
reporter: reporter,
|
|
155
|
+
source_location: annotation.location
|
|
156
|
+
)
|
|
141
157
|
effects << effect if effect
|
|
142
158
|
end
|
|
143
159
|
effects.uniq
|
|
@@ -168,7 +184,7 @@ module Rigor
|
|
|
168
184
|
/x
|
|
169
185
|
private_constant :PREDICATE_DIRECTIVE_PATTERN
|
|
170
186
|
|
|
171
|
-
def parse_predicate_annotation(string)
|
|
187
|
+
def parse_predicate_annotation(string, name_scope: nil, reporter: nil, source_location: nil)
|
|
172
188
|
match = PREDICATE_DIRECTIVE_PATTERN.match(string)
|
|
173
189
|
return nil if match.nil?
|
|
174
190
|
|
|
@@ -176,8 +192,16 @@ module Rigor
|
|
|
176
192
|
target = match[:target].to_s
|
|
177
193
|
edge = directive == "predicate-if-true" ? :truthy_only : :falsey_only
|
|
178
194
|
target_kind, target_name = target_fields(target)
|
|
179
|
-
class_name, refinement_type, negative = resolve_directive_rhs(
|
|
180
|
-
|
|
195
|
+
class_name, refinement_type, negative = resolve_directive_rhs(
|
|
196
|
+
match,
|
|
197
|
+
name_scope: name_scope,
|
|
198
|
+
reporter: reporter,
|
|
199
|
+
source_location: source_location
|
|
200
|
+
)
|
|
201
|
+
if class_name.nil? && refinement_type.nil?
|
|
202
|
+
record_unresolved(reporter, string, source_location)
|
|
203
|
+
return nil
|
|
204
|
+
end
|
|
181
205
|
|
|
182
206
|
PredicateEffect.new(
|
|
183
207
|
edge: edge,
|
|
@@ -194,15 +218,26 @@ module Rigor
|
|
|
194
218
|
# `RBS::Definition::Method#annotations`. Returns an empty
|
|
195
219
|
# array when no recognised assertion directives are
|
|
196
220
|
# attached to the method.
|
|
197
|
-
|
|
221
|
+
#
|
|
222
|
+
# See {.read_predicate_effects} for the `environment:`
|
|
223
|
+
# keyword contract.
|
|
224
|
+
def read_assert_effects(method_def, environment: nil)
|
|
198
225
|
return [] if method_def.nil?
|
|
199
226
|
|
|
200
227
|
annotations = method_def.annotations
|
|
201
228
|
return [] if annotations.nil? || annotations.empty?
|
|
202
229
|
|
|
230
|
+
name_scope = environment&.name_scope
|
|
231
|
+
reporter = environment&.rbs_extended_reporter
|
|
232
|
+
|
|
203
233
|
effects = []
|
|
204
234
|
annotations.each do |annotation|
|
|
205
|
-
effect = parse_assert_annotation(
|
|
235
|
+
effect = parse_assert_annotation(
|
|
236
|
+
annotation.string,
|
|
237
|
+
name_scope: name_scope,
|
|
238
|
+
reporter: reporter,
|
|
239
|
+
source_location: annotation.location
|
|
240
|
+
)
|
|
206
241
|
effects << effect if effect
|
|
207
242
|
end
|
|
208
243
|
effects.uniq
|
|
@@ -232,7 +267,7 @@ module Rigor
|
|
|
232
267
|
}.freeze
|
|
233
268
|
private_constant :ASSERT_CONDITIONS
|
|
234
269
|
|
|
235
|
-
def parse_assert_annotation(string)
|
|
270
|
+
def parse_assert_annotation(string, name_scope: nil, reporter: nil, source_location: nil)
|
|
236
271
|
match = ASSERT_DIRECTIVE_PATTERN.match(string)
|
|
237
272
|
return nil if match.nil?
|
|
238
273
|
|
|
@@ -242,8 +277,16 @@ module Rigor
|
|
|
242
277
|
|
|
243
278
|
target = match[:target].to_s
|
|
244
279
|
target_kind, target_name = target_fields(target)
|
|
245
|
-
class_name, refinement_type, negative = resolve_directive_rhs(
|
|
246
|
-
|
|
280
|
+
class_name, refinement_type, negative = resolve_directive_rhs(
|
|
281
|
+
match,
|
|
282
|
+
name_scope: name_scope,
|
|
283
|
+
reporter: reporter,
|
|
284
|
+
source_location: source_location
|
|
285
|
+
)
|
|
286
|
+
if class_name.nil? && refinement_type.nil?
|
|
287
|
+
record_unresolved(reporter, string, source_location)
|
|
288
|
+
return nil
|
|
289
|
+
end
|
|
247
290
|
|
|
248
291
|
AssertEffect.new(
|
|
249
292
|
condition: condition,
|
|
@@ -273,7 +316,7 @@ module Rigor
|
|
|
273
316
|
# - Refinement payload unparseable: returns
|
|
274
317
|
# `[nil, nil, false]` so callers can drop the directive
|
|
275
318
|
# silently (fail-soft policy).
|
|
276
|
-
def resolve_directive_rhs(match)
|
|
319
|
+
def resolve_directive_rhs(match, name_scope: nil, reporter: nil, source_location: nil)
|
|
277
320
|
negative = match[:negation].to_s == "~"
|
|
278
321
|
class_capture = match[:class_name]
|
|
279
322
|
return [class_capture.to_s.sub(/\A::/, ""), nil, negative] if class_capture
|
|
@@ -281,7 +324,12 @@ module Rigor
|
|
|
281
324
|
refinement_capture = match[:refinement]
|
|
282
325
|
return [nil, nil, false] if refinement_capture.nil?
|
|
283
326
|
|
|
284
|
-
type = Builtins::ImportedRefinements.parse(
|
|
327
|
+
type = Builtins::ImportedRefinements.parse(
|
|
328
|
+
refinement_capture,
|
|
329
|
+
name_scope: name_scope,
|
|
330
|
+
reporter: reporter,
|
|
331
|
+
source_location: source_location
|
|
332
|
+
)
|
|
285
333
|
return [nil, nil, false] if type.nil?
|
|
286
334
|
|
|
287
335
|
[nil, type, negative]
|
|
@@ -326,16 +374,25 @@ module Rigor
|
|
|
326
374
|
# - the directive's payload names a refinement not
|
|
327
375
|
# registered in `Rigor::Builtins::ImportedRefinements`
|
|
328
376
|
# (the analyzer prefers a silent miss over crashing on a
|
|
329
|
-
# typo;
|
|
330
|
-
# `:
|
|
331
|
-
|
|
377
|
+
# typo; ADR-13 slice 3b surfaces the miss as a
|
|
378
|
+
# `dynamic.rbs-extended.unresolved` `:info` diagnostic when
|
|
379
|
+
# an `environment:` is supplied).
|
|
380
|
+
def read_return_type_override(method_def, environment: nil)
|
|
332
381
|
return nil if method_def.nil?
|
|
333
382
|
|
|
334
383
|
annotations = method_def.annotations
|
|
335
384
|
return nil if annotations.nil? || annotations.empty?
|
|
336
385
|
|
|
386
|
+
name_scope = environment&.name_scope
|
|
387
|
+
reporter = environment&.rbs_extended_reporter
|
|
388
|
+
|
|
337
389
|
annotations.each do |annotation|
|
|
338
|
-
type = parse_return_type_override(
|
|
390
|
+
type = parse_return_type_override(
|
|
391
|
+
annotation.string,
|
|
392
|
+
name_scope: name_scope,
|
|
393
|
+
reporter: reporter,
|
|
394
|
+
source_location: annotation.location
|
|
395
|
+
)
|
|
339
396
|
return type if type
|
|
340
397
|
end
|
|
341
398
|
nil
|
|
@@ -360,11 +417,18 @@ module Rigor
|
|
|
360
417
|
/x
|
|
361
418
|
private_constant :RETURN_DIRECTIVE_PATTERN
|
|
362
419
|
|
|
363
|
-
def parse_return_type_override(string)
|
|
420
|
+
def parse_return_type_override(string, name_scope: nil, reporter: nil, source_location: nil)
|
|
364
421
|
match = RETURN_DIRECTIVE_PATTERN.match(string)
|
|
365
422
|
return nil if match.nil?
|
|
366
423
|
|
|
367
|
-
Builtins::ImportedRefinements.parse(
|
|
424
|
+
type = Builtins::ImportedRefinements.parse(
|
|
425
|
+
match[:payload],
|
|
426
|
+
name_scope: name_scope,
|
|
427
|
+
reporter: reporter,
|
|
428
|
+
source_location: source_location
|
|
429
|
+
)
|
|
430
|
+
record_unresolved(reporter, string, source_location) if type.nil?
|
|
431
|
+
type
|
|
368
432
|
end
|
|
369
433
|
|
|
370
434
|
# Returned for `rigor:v1:param: <name> <refinement>`. The
|
|
@@ -397,13 +461,23 @@ module Rigor
|
|
|
397
461
|
# purposes; passing a too-wide `Nominal[String]` argument
|
|
398
462
|
# is flagged as an argument-type mismatch at the call
|
|
399
463
|
# site.
|
|
400
|
-
def read_param_type_overrides(method_def)
|
|
464
|
+
def read_param_type_overrides(method_def, environment: nil)
|
|
401
465
|
return [] if method_def.nil?
|
|
402
466
|
|
|
403
467
|
annotations = method_def.annotations
|
|
404
468
|
return [] if annotations.nil? || annotations.empty?
|
|
405
469
|
|
|
406
|
-
|
|
470
|
+
name_scope = environment&.name_scope
|
|
471
|
+
reporter = environment&.rbs_extended_reporter
|
|
472
|
+
|
|
473
|
+
annotations.filter_map do |annotation|
|
|
474
|
+
parse_param_annotation(
|
|
475
|
+
annotation.string,
|
|
476
|
+
name_scope: name_scope,
|
|
477
|
+
reporter: reporter,
|
|
478
|
+
source_location: annotation.location
|
|
479
|
+
)
|
|
480
|
+
end
|
|
407
481
|
end
|
|
408
482
|
|
|
409
483
|
# Convenience reader for call sites that want to look up
|
|
@@ -411,8 +485,10 @@ module Rigor
|
|
|
411
485
|
# Hash<Symbol, Rigor::Type>; missing keys mean "use the
|
|
412
486
|
# RBS-declared type". Callers MUST treat the hash as
|
|
413
487
|
# read-only.
|
|
414
|
-
def param_type_override_map(method_def)
|
|
415
|
-
read_param_type_overrides(method_def
|
|
488
|
+
def param_type_override_map(method_def, environment: nil)
|
|
489
|
+
read_param_type_overrides(method_def, environment: environment)
|
|
490
|
+
.to_h { |o| [o.param_name, o.type] }
|
|
491
|
+
.freeze
|
|
416
492
|
end
|
|
417
493
|
|
|
418
494
|
# The `is` glue word is optional so authors can write
|
|
@@ -434,12 +510,20 @@ module Rigor
|
|
|
434
510
|
/x
|
|
435
511
|
private_constant :PARAM_DIRECTIVE_PATTERN
|
|
436
512
|
|
|
437
|
-
def parse_param_annotation(string)
|
|
513
|
+
def parse_param_annotation(string, name_scope: nil, reporter: nil, source_location: nil)
|
|
438
514
|
match = PARAM_DIRECTIVE_PATTERN.match(string)
|
|
439
515
|
return nil if match.nil?
|
|
440
516
|
|
|
441
|
-
type = Builtins::ImportedRefinements.parse(
|
|
442
|
-
|
|
517
|
+
type = Builtins::ImportedRefinements.parse(
|
|
518
|
+
match[:payload],
|
|
519
|
+
name_scope: name_scope,
|
|
520
|
+
reporter: reporter,
|
|
521
|
+
source_location: source_location
|
|
522
|
+
)
|
|
523
|
+
if type.nil?
|
|
524
|
+
record_unresolved(reporter, string, source_location)
|
|
525
|
+
return nil
|
|
526
|
+
end
|
|
443
527
|
|
|
444
528
|
ParamOverride.new(param_name: match[:param].to_sym, type: type)
|
|
445
529
|
end
|
|
@@ -477,18 +561,21 @@ module Rigor
|
|
|
477
561
|
# Returns `nil` when the method carries no recognised
|
|
478
562
|
# contribution directives (callers can skip the merge step
|
|
479
563
|
# without iterating an empty bundle).
|
|
480
|
-
|
|
564
|
+
#
|
|
565
|
+
# See {.read_predicate_effects} for the `environment:`
|
|
566
|
+
# keyword contract.
|
|
567
|
+
def read_flow_contribution(method_def, environment: nil)
|
|
481
568
|
return nil if method_def.nil?
|
|
482
569
|
|
|
483
|
-
predicate_effects = read_predicate_effects(method_def)
|
|
484
|
-
assert_effects = read_assert_effects(method_def)
|
|
485
|
-
return_override = read_return_type_override(method_def)
|
|
570
|
+
predicate_effects = read_predicate_effects(method_def, environment: environment)
|
|
571
|
+
assert_effects = read_assert_effects(method_def, environment: environment)
|
|
572
|
+
return_override = read_return_type_override(method_def, environment: environment)
|
|
486
573
|
return nil if predicate_effects.empty? && assert_effects.empty? && return_override.nil?
|
|
487
574
|
|
|
488
575
|
build_flow_contribution(predicate_effects, assert_effects, return_override)
|
|
489
576
|
end
|
|
490
577
|
|
|
491
|
-
def build_flow_contribution(predicate_effects, assert_effects, return_override)
|
|
578
|
+
def build_flow_contribution(predicate_effects, assert_effects, return_override)
|
|
492
579
|
truthy = predicate_effects.select(&:truthy_only?).map(&:to_fact)
|
|
493
580
|
falsey = predicate_effects.select(&:falsey_only?).map(&:to_fact)
|
|
494
581
|
post_return = []
|
|
@@ -513,5 +600,17 @@ module Rigor
|
|
|
513
600
|
def nilable_slot(facts)
|
|
514
601
|
facts.empty? ? nil : facts
|
|
515
602
|
end
|
|
603
|
+
|
|
604
|
+
# ADR-13 slice 3b — guards every reporter call so the
|
|
605
|
+
# in-RbsExtended-module call sites can record events
|
|
606
|
+
# uniformly without nil-checking each time. When the
|
|
607
|
+
# reporter is nil (the v0.1.0 → v0.1.3 default for call
|
|
608
|
+
# sites that do not yet thread `environment:`), the call is
|
|
609
|
+
# a no-op and the parser stays fail-soft.
|
|
610
|
+
def record_unresolved(reporter, payload, source_location)
|
|
611
|
+
return if reporter.nil?
|
|
612
|
+
|
|
613
|
+
reporter.record_unresolved(payload: payload, source_location: source_location)
|
|
614
|
+
end
|
|
516
615
|
end
|
|
517
616
|
end
|
data/lib/rigor/scope.rb
CHANGED
|
@@ -20,7 +20,8 @@ module Rigor
|
|
|
20
20
|
:ivars, :cvars, :globals,
|
|
21
21
|
:class_ivars, :class_cvars, :program_globals,
|
|
22
22
|
:discovered_classes, :in_source_constants, :discovered_methods,
|
|
23
|
-
:discovered_def_nodes, :discovered_method_visibilities
|
|
23
|
+
:discovered_def_nodes, :discovered_method_visibilities,
|
|
24
|
+
:source_path
|
|
24
25
|
|
|
25
26
|
EMPTY_DECLARED_TYPES = {}.compare_by_identity.freeze
|
|
26
27
|
EMPTY_VAR_BINDINGS = {}.freeze
|
|
@@ -28,8 +29,9 @@ module Rigor
|
|
|
28
29
|
private_constant :EMPTY_DECLARED_TYPES, :EMPTY_VAR_BINDINGS, :EMPTY_CLASS_BINDINGS
|
|
29
30
|
|
|
30
31
|
class << self
|
|
31
|
-
def empty(environment: Environment.default)
|
|
32
|
-
new(environment: environment, locals: {}.freeze,
|
|
32
|
+
def empty(environment: Environment.default, source_path: nil)
|
|
33
|
+
new(environment: environment, locals: {}.freeze,
|
|
34
|
+
fact_store: Analysis::FactStore.empty, source_path: source_path)
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
|
|
@@ -48,7 +50,8 @@ module Rigor
|
|
|
48
50
|
in_source_constants: EMPTY_VAR_BINDINGS,
|
|
49
51
|
discovered_methods: EMPTY_CLASS_BINDINGS,
|
|
50
52
|
discovered_def_nodes: EMPTY_CLASS_BINDINGS,
|
|
51
|
-
discovered_method_visibilities: EMPTY_CLASS_BINDINGS
|
|
53
|
+
discovered_method_visibilities: EMPTY_CLASS_BINDINGS,
|
|
54
|
+
source_path: nil
|
|
52
55
|
)
|
|
53
56
|
@environment = environment
|
|
54
57
|
@locals = locals
|
|
@@ -66,6 +69,7 @@ module Rigor
|
|
|
66
69
|
@discovered_methods = discovered_methods
|
|
67
70
|
@discovered_def_nodes = discovered_def_nodes
|
|
68
71
|
@discovered_method_visibilities = discovered_method_visibilities
|
|
72
|
+
@source_path = source_path
|
|
69
73
|
freeze
|
|
70
74
|
end
|
|
71
75
|
|
|
@@ -92,6 +96,16 @@ module Rigor
|
|
|
92
96
|
rebuild(self_type: type)
|
|
93
97
|
end
|
|
94
98
|
|
|
99
|
+
# ADR-11 per-call-site assertion gating prerequisite. The
|
|
100
|
+
# analyzer's per-file boundary stamps the current source
|
|
101
|
+
# file's path onto the seed scope; nested rebuilds carry
|
|
102
|
+
# the value through so plugin hooks like
|
|
103
|
+
# `flow_contribution_for` can resolve "which file does
|
|
104
|
+
# this call site belong to?" without thread-locals.
|
|
105
|
+
def with_source_path(path)
|
|
106
|
+
rebuild(source_path: path)
|
|
107
|
+
end
|
|
108
|
+
|
|
95
109
|
# Slice A-declarations. Returns a scope that carries an
|
|
96
110
|
# identity-comparing Hash of `Prism::Node => Rigor::Type`
|
|
97
111
|
# overrides. `ExpressionTyper#type_of(node)` MUST consult
|
|
@@ -333,7 +347,7 @@ module Rigor
|
|
|
333
347
|
build_joined_scope(joined_locals, joined_ivars, joined_cvars, joined_globals, other)
|
|
334
348
|
end
|
|
335
349
|
|
|
336
|
-
def ==(other)
|
|
350
|
+
def ==(other)
|
|
337
351
|
other.is_a?(Scope) &&
|
|
338
352
|
environment.equal?(other.environment) &&
|
|
339
353
|
@locals == other.locals &&
|
|
@@ -357,7 +371,8 @@ module Rigor
|
|
|
357
371
|
class_ivars: @class_ivars, class_cvars: @class_cvars, program_globals: @program_globals,
|
|
358
372
|
discovered_classes: @discovered_classes, in_source_constants: @in_source_constants,
|
|
359
373
|
discovered_methods: @discovered_methods, discovered_def_nodes: @discovered_def_nodes,
|
|
360
|
-
discovered_method_visibilities: @discovered_method_visibilities
|
|
374
|
+
discovered_method_visibilities: @discovered_method_visibilities,
|
|
375
|
+
source_path: @source_path
|
|
361
376
|
)
|
|
362
377
|
self.class.new(
|
|
363
378
|
environment: environment, locals: locals,
|
|
@@ -370,7 +385,8 @@ module Rigor
|
|
|
370
385
|
in_source_constants: in_source_constants,
|
|
371
386
|
discovered_methods: discovered_methods,
|
|
372
387
|
discovered_def_nodes: discovered_def_nodes,
|
|
373
|
-
discovered_method_visibilities: discovered_method_visibilities
|
|
388
|
+
discovered_method_visibilities: discovered_method_visibilities,
|
|
389
|
+
source_path: source_path
|
|
374
390
|
)
|
|
375
391
|
end
|
|
376
392
|
|
|
@@ -396,7 +412,8 @@ module Rigor
|
|
|
396
412
|
in_source_constants: in_source_constants,
|
|
397
413
|
discovered_methods: discovered_methods,
|
|
398
414
|
discovered_def_nodes: discovered_def_nodes,
|
|
399
|
-
discovered_method_visibilities: discovered_method_visibilities
|
|
415
|
+
discovered_method_visibilities: discovered_method_visibilities,
|
|
416
|
+
source_path: source_path
|
|
400
417
|
)
|
|
401
418
|
end
|
|
402
419
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module SigGen
|
|
5
|
+
# The five classifications a candidate method falls into
|
|
6
|
+
# after the generator has compared the inferred return
|
|
7
|
+
# type against the project's existing RBS.
|
|
8
|
+
#
|
|
9
|
+
# The strings are the diagnostic-family identifiers ADR-14
|
|
10
|
+
# reserves under `sig.*`; the MVP carries them as plain
|
|
11
|
+
# symbols on the method candidate and renders the matching
|
|
12
|
+
# identifier in JSON / text output. They are added to the
|
|
13
|
+
# diagnostic family hierarchy in `docs/type-specification/
|
|
14
|
+
# diagnostic-policy.md` even though slice 1 does not yet
|
|
15
|
+
# emit them as diagnostics.
|
|
16
|
+
module Classification
|
|
17
|
+
NEW_FILE = :new_file
|
|
18
|
+
NEW_METHOD = :new_method
|
|
19
|
+
TIGHTER_RETURN = :tighter_return
|
|
20
|
+
EQUIVALENT = :equivalent
|
|
21
|
+
SKIPPED = :skipped
|
|
22
|
+
|
|
23
|
+
DIAGNOSTIC_IDS = {
|
|
24
|
+
NEW_FILE => "sig.generated.new-file",
|
|
25
|
+
NEW_METHOD => "sig.generated.new-method",
|
|
26
|
+
TIGHTER_RETURN => "sig.generated.tighter-return"
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
SKIP_DIAGNOSTIC_IDS = {
|
|
30
|
+
complex_shape: "sig.skipped.complex-shape",
|
|
31
|
+
user_authored: "sig.skipped.user-authored",
|
|
32
|
+
untyped_return: "sig.skipped.untyped-return"
|
|
33
|
+
}.freeze
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|