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
|
@@ -18,7 +18,7 @@ module Rigor
|
|
|
18
18
|
relational
|
|
19
19
|
].freeze
|
|
20
20
|
|
|
21
|
-
Target
|
|
21
|
+
class Target < Data.define(:kind, :name)
|
|
22
22
|
def self.local(name)
|
|
23
23
|
new(kind: :local, name: name.to_sym)
|
|
24
24
|
end
|
|
@@ -28,7 +28,7 @@ module Rigor
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
Fact
|
|
31
|
+
class Fact < Data.define(:bucket, :target, :predicate, :payload, :polarity, :stability)
|
|
32
32
|
def initialize(bucket:, target:, predicate:, payload: nil, polarity: :positive, stability: :local_binding)
|
|
33
33
|
bucket = bucket.to_sym
|
|
34
34
|
raise ArgumentError, "unknown fact bucket #{bucket.inspect}" unless BUCKETS.include?(bucket)
|
|
@@ -125,8 +125,16 @@ module Rigor
|
|
|
125
125
|
unique.freeze
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
+
# `fact.target` is `Target | Array[Target]` per the carrier
|
|
129
|
+
# contract. Branching with an early return on the `Array`
|
|
130
|
+
# arm lets type narrowing collapse the post-return value to
|
|
131
|
+
# the bare `Target` case, so the wrapped tuple is `[Target]`
|
|
132
|
+
# and the union of return paths is exactly `Array[Target]`.
|
|
128
133
|
def fact_targets(fact)
|
|
129
|
-
|
|
134
|
+
target = fact.target
|
|
135
|
+
return target if target.is_a?(Array)
|
|
136
|
+
|
|
137
|
+
[target]
|
|
130
138
|
end
|
|
131
139
|
end
|
|
132
140
|
end
|
|
@@ -31,8 +31,8 @@ module Rigor
|
|
|
31
31
|
# from `Configuration::SeverityProfile::PROFILES`.
|
|
32
32
|
# - `since` — first version the rule shipped in.
|
|
33
33
|
module RuleCatalog # rubocop:disable Metrics/ModuleLength
|
|
34
|
-
Entry
|
|
35
|
-
|
|
34
|
+
class Entry < Data.define(:id, :summary, :fires_when, :does_not_fire_when,
|
|
35
|
+
:suppression, :severity_authored, :severity_by_profile, :since)
|
|
36
36
|
def aliases
|
|
37
37
|
CheckRules::LEGACY_RULE_ALIASES.select { |_legacy, canonical| canonical == id }.keys
|
|
38
38
|
end
|
|
@@ -6,6 +6,7 @@ require_relative "../environment"
|
|
|
6
6
|
require_relative "../scope"
|
|
7
7
|
require_relative "../cache/store"
|
|
8
8
|
require_relative "../plugin"
|
|
9
|
+
require_relative "../rbs_extended/reporter"
|
|
9
10
|
require_relative "../reflection"
|
|
10
11
|
require_relative "../type/combinator"
|
|
11
12
|
require_relative "../inference/coverage_scanner"
|
|
@@ -22,7 +23,8 @@ module Rigor
|
|
|
22
23
|
RUBY_GLOB = "**/*.rb"
|
|
23
24
|
DEFAULT_CACHE_ROOT = ".rigor/cache"
|
|
24
25
|
|
|
25
|
-
attr_reader :cache_store, :plugin_registry, :dependency_source_index
|
|
26
|
+
attr_reader :cache_store, :plugin_registry, :dependency_source_index,
|
|
27
|
+
:rbs_extended_reporter, :boundary_cross_reporter
|
|
26
28
|
|
|
27
29
|
# @param configuration [Rigor::Configuration]
|
|
28
30
|
# @param explain [Boolean] surface fail-soft fallback events
|
|
@@ -42,6 +44,8 @@ module Rigor
|
|
|
42
44
|
@plugin_requirer = plugin_requirer
|
|
43
45
|
@plugin_registry = Plugin::Registry::EMPTY
|
|
44
46
|
@dependency_source_index = DependencySourceInference::Index::EMPTY
|
|
47
|
+
@rbs_extended_reporter = RbsExtended::Reporter.new
|
|
48
|
+
@boundary_cross_reporter = DependencySourceInference::BoundaryCrossReporter.new
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
# Walks every Ruby file under `paths`, parses it, builds a
|
|
@@ -66,12 +70,16 @@ module Rigor
|
|
|
66
70
|
signature_paths: @configuration.signature_paths,
|
|
67
71
|
cache_store: @cache_store,
|
|
68
72
|
plugin_registry: @plugin_registry,
|
|
69
|
-
dependency_source_index: @dependency_source_index
|
|
73
|
+
dependency_source_index: @dependency_source_index,
|
|
74
|
+
rbs_extended_reporter: @rbs_extended_reporter,
|
|
75
|
+
boundary_cross_reporter: @boundary_cross_reporter
|
|
70
76
|
)
|
|
71
77
|
expansion = expand_paths(paths)
|
|
72
78
|
|
|
73
79
|
diagnostics = pre_file_diagnostics(expansion)
|
|
74
80
|
diagnostics += expansion.fetch(:files).flat_map { |path| analyze_file(path, environment) }
|
|
81
|
+
diagnostics += rbs_extended_reporter_diagnostics
|
|
82
|
+
diagnostics += boundary_cross_diagnostics
|
|
75
83
|
|
|
76
84
|
Result.new(diagnostics: apply_severity_profile(diagnostics))
|
|
77
85
|
end
|
|
@@ -296,6 +304,109 @@ module Rigor
|
|
|
296
304
|
end
|
|
297
305
|
end
|
|
298
306
|
|
|
307
|
+
# ADR-13 slice 3b — drains the per-run
|
|
308
|
+
# {RbsExtended::Reporter} into one diagnostic per accumulated
|
|
309
|
+
# event:
|
|
310
|
+
#
|
|
311
|
+
# - `dynamic.rbs-extended.unresolved` for every annotation
|
|
312
|
+
# payload the parser could not turn into a {Rigor::Type}.
|
|
313
|
+
# Surfaces typos and references to plugin-supplied names
|
|
314
|
+
# the project did not enable.
|
|
315
|
+
# - `dynamic.shape.lossy-projection` for every shape-projection
|
|
316
|
+
# type function (`pick_of`, …) applied to a carrier that
|
|
317
|
+
# loses precision (anything other than `HashShape` / `Tuple`).
|
|
318
|
+
#
|
|
319
|
+
# Both are authored `:info`; the severity profile re-stamps
|
|
320
|
+
# them per project taste. Path / line / column come from the
|
|
321
|
+
# annotation's `RBS::Location` when available, falling back
|
|
322
|
+
# to `.rigor.yml`-style file-level attribution otherwise.
|
|
323
|
+
def rbs_extended_reporter_diagnostics
|
|
324
|
+
return [] if @rbs_extended_reporter.empty?
|
|
325
|
+
|
|
326
|
+
unresolved = @rbs_extended_reporter.unresolved_payloads.map do |entry|
|
|
327
|
+
build_reporter_diagnostic(
|
|
328
|
+
entry.source_location,
|
|
329
|
+
rule: "dynamic.rbs-extended.unresolved",
|
|
330
|
+
message: "`RBS::Extended` directive payload could not be resolved: " \
|
|
331
|
+
"#{entry.payload.inspect}. Check for typos or enable a plugin " \
|
|
332
|
+
"that contributes the referenced type vocabulary."
|
|
333
|
+
)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
lossy = @rbs_extended_reporter.lossy_projections.map do |entry|
|
|
337
|
+
build_reporter_diagnostic(
|
|
338
|
+
entry.source_location,
|
|
339
|
+
rule: "dynamic.shape.lossy-projection",
|
|
340
|
+
message: "Shape projection `#{entry.head}` applied to a carrier without a " \
|
|
341
|
+
"literal shape; the projection degrades to the input type. Author " \
|
|
342
|
+
"a `HashShape` / `Tuple` carrier or accept the unchanged result."
|
|
343
|
+
)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
unresolved + lossy
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# ADR-10 slice 5c — drains the per-run
|
|
350
|
+
# {DependencySourceInference::BoundaryCrossReporter} into
|
|
351
|
+
# `dynamic.dependency-source.boundary-cross` `:info`
|
|
352
|
+
# diagnostics. Each event flags a call site where RBS
|
|
353
|
+
# dispatch produced a concrete answer AND a `mode: :full`
|
|
354
|
+
# opt-in gem's source catalog ALSO contains an entry for
|
|
355
|
+
# the same `(class_name, method_name)` — i.e., both
|
|
356
|
+
# contracts have an opinion. RBS still wins on the
|
|
357
|
+
# dispatch result; the diagnostic is purely advisory so
|
|
358
|
+
# the user can verify the two contracts haven't drifted.
|
|
359
|
+
#
|
|
360
|
+
# Severity profile re-stamps the rule per project taste.
|
|
361
|
+
# The diagnostic carries no `path` / `line` / `column`
|
|
362
|
+
# because the crossing is per-method-per-gem, not
|
|
363
|
+
# per-call-site — the diagnostic anchors at `.rigor.yml`
|
|
364
|
+
# like the other `dependency-source.*` diagnostics that
|
|
365
|
+
# report on opt-in configuration.
|
|
366
|
+
def boundary_cross_diagnostics
|
|
367
|
+
return [] if @boundary_cross_reporter.empty?
|
|
368
|
+
|
|
369
|
+
@boundary_cross_reporter.entries.map do |entry|
|
|
370
|
+
Diagnostic.new(
|
|
371
|
+
path: ".rigor.yml", line: 1, column: 1,
|
|
372
|
+
message: "`#{entry.class_name}##{entry.method_name}` is contributed by both " \
|
|
373
|
+
"RBS (#{entry.rbs_display}) and the `mode: :full` opt-in gem " \
|
|
374
|
+
"`#{entry.gem_name}`. RBS wins on dispatch; verify the gem source " \
|
|
375
|
+
"has not drifted from its RBS contract.",
|
|
376
|
+
severity: :info,
|
|
377
|
+
rule: "dynamic.dependency-source.boundary-cross",
|
|
378
|
+
source_family: :builtin
|
|
379
|
+
)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def build_reporter_diagnostic(source_location, rule:, message:)
|
|
384
|
+
path, line, column = location_fields(source_location)
|
|
385
|
+
Diagnostic.new(
|
|
386
|
+
path: path, line: line, column: column,
|
|
387
|
+
message: message, severity: :info, rule: rule, source_family: :builtin
|
|
388
|
+
)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def location_fields(source_location)
|
|
392
|
+
return [".rigor.yml", 1, 1] if source_location.nil?
|
|
393
|
+
|
|
394
|
+
path = location_path(source_location)
|
|
395
|
+
line = source_location.respond_to?(:start_line) ? source_location.start_line : 1
|
|
396
|
+
column = source_location.respond_to?(:start_column) ? source_location.start_column + 1 : 1
|
|
397
|
+
[path, line, column]
|
|
398
|
+
rescue StandardError
|
|
399
|
+
[".rigor.yml", 1, 1]
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def location_path(source_location)
|
|
403
|
+
buffer = source_location.respond_to?(:buffer) ? source_location.buffer : nil
|
|
404
|
+
return ".rigor.yml" if buffer.nil? || !buffer.respond_to?(:name)
|
|
405
|
+
|
|
406
|
+
name = buffer.name.to_s
|
|
407
|
+
name.empty? ? ".rigor.yml" : name
|
|
408
|
+
end
|
|
409
|
+
|
|
299
410
|
# ADR-9 slice 3 — invokes every loaded plugin's `#prepare`
|
|
300
411
|
# hook once per run, after the loader's `#init` pass and
|
|
301
412
|
# before per-file iteration. Plugins publish facts here
|
|
@@ -453,7 +564,7 @@ module Rigor
|
|
|
453
564
|
parse_result = Prism.parse_file(path, version: @configuration.target_ruby)
|
|
454
565
|
return parse_diagnostics(path, parse_result) unless parse_result.errors.empty?
|
|
455
566
|
|
|
456
|
-
scope = Scope.empty(environment: environment)
|
|
567
|
+
scope = Scope.empty(environment: environment, source_path: path)
|
|
457
568
|
index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope)
|
|
458
569
|
diagnostics = CheckRules.diagnose(
|
|
459
570
|
path: path,
|