rigortype 0.3.0 → 0.3.2
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 +10 -10
- data/data/builtins/ruby_core/array.yml +416 -392
- data/data/builtins/ruby_core/file.yml +42 -42
- data/data/builtins/ruby_core/hash.yml +302 -302
- data/data/builtins/ruby_core/io.yml +191 -191
- data/data/builtins/ruby_core/numeric.yml +321 -366
- data/data/builtins/ruby_core/proc.yml +124 -124
- data/data/builtins/ruby_core/range.yml +21 -21
- data/data/builtins/ruby_core/rational.yml +39 -39
- data/data/builtins/ruby_core/re.yml +65 -65
- data/data/builtins/ruby_core/set.yml +106 -106
- data/data/builtins/ruby_core/struct.yml +14 -14
- data/data/core_overlay/pathname.rbs +5 -0
- data/data/core_overlay/resolv.rbs +31 -0
- data/data/core_overlay/string_scanner.rbs +11 -5
- data/data/gem_overlay/activesupport/core_ext.rbs +10 -6
- data/data/vendored_gem_sigs/bundler/bundler.rbs +39 -27
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +6 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +7 -0
- data/data/vendored_gem_sigs/racc/racc.rbs +67 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +20 -9
- data/docs/handbook/01-getting-started.md +22 -34
- data/docs/handbook/06-classes.md +1 -1
- data/docs/handbook/07-rbs-and-extended.md +76 -101
- data/docs/handbook/08-understanding-errors.md +114 -247
- data/docs/handbook/09-plugins.md +54 -144
- data/docs/handbook/README.md +5 -3
- data/docs/handbook/appendix-liskov.md +4 -2
- data/docs/handbook/appendix-phpstan.md +14 -7
- data/docs/handbook/appendix-steep.md +4 -2
- data/docs/handbook/appendix-type-theory.md +3 -1
- data/docs/manual/02-cli-reference.md +103 -11
- data/docs/manual/03-configuration.md +16 -1
- data/docs/manual/04-diagnostics.md +36 -4
- data/docs/manual/06-baseline.md +35 -1
- data/docs/manual/08-skills.md +6 -1
- data/docs/manual/09-editor-integration.md +3 -2
- data/docs/manual/12-caching.md +17 -5
- data/docs/manual/15-type-protection-coverage.md +122 -1
- data/docs/manual/plugins/rigor-actioncable.md +32 -0
- data/docs/manual/plugins/rigor-devise.md +4 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +22 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -0
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +79 -19
- data/lib/rigor/analysis/incremental_session.rb +156 -11
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +14 -0
- data/lib/rigor/analysis/run_cache_key.rb +27 -1
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +201 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +28 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +7 -10
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -1
- data/lib/rigor/analysis/runner.rb +80 -8
- data/lib/rigor/bleeding_edge.rb +132 -7
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +162 -0
- data/lib/rigor/cache/incremental_snapshot.rb +87 -18
- data/lib/rigor/cache/rbs_cache_producer.rb +11 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +38 -0
- data/lib/rigor/cache/store.rb +99 -24
- data/lib/rigor/cli/check_command.rb +61 -34
- data/lib/rigor/cli/check_invocation.rb +84 -0
- data/lib/rigor/cli/coverage_command.rb +15 -2
- data/lib/rigor/cli/coverage_mutation.rb +242 -11
- data/lib/rigor/cli/doctor_command.rb +6 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +10 -0
- data/lib/rigor/cli/fused_protection_report.rb +12 -3
- data/lib/rigor/cli/mutation_fork_scan.rb +64 -0
- data/lib/rigor/cli/mutation_protection_renderer.rb +12 -0
- data/lib/rigor/cli/mutation_protection_report.rb +26 -5
- data/lib/rigor/cli/protection_renderer.rb +13 -0
- data/lib/rigor/cli/protection_report.rb +11 -3
- data/lib/rigor/cli/show_bleedingedge_command.rb +17 -4
- data/lib/rigor/cli/sig_gen_command.rb +5 -4
- data/lib/rigor/cli/skill_command.rb +21 -1
- data/lib/rigor/cli/skill_deep_probe.rb +172 -0
- data/lib/rigor/cli/skill_describe.rb +75 -9
- data/lib/rigor/configuration.rb +45 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +2 -1
- data/lib/rigor/environment/default_libraries.rb +5 -4
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +434 -75
- data/lib/rigor/environment.rb +38 -14
- data/lib/rigor/inference/expression_typer.rb +15 -0
- data/lib/rigor/inference/fork_map.rb +9 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +50 -2
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +35 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +6 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +16 -0
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +64 -5
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +137 -9
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +124 -1
- data/lib/rigor/inference/mutation_widening.rb +30 -25
- data/lib/rigor/inference/narrowing.rb +3 -1
- data/lib/rigor/inference/optimistic_origin.rb +58 -0
- data/lib/rigor/inference/protection_scanner.rb +18 -2
- data/lib/rigor/inference/receiver_alias.rb +57 -0
- data/lib/rigor/inference/scope_indexer.rb +78 -27
- data/lib/rigor/inference/statement_evaluator.rb +40 -2
- data/lib/rigor/language_server/buffer_resolution.rb +6 -3
- data/lib/rigor/language_server/buffer_table.rb +68 -6
- data/lib/rigor/language_server/diagnostic_publisher.rb +207 -4
- data/lib/rigor/language_server/incremental_sync.rb +159 -0
- data/lib/rigor/language_server/project_context.rb +45 -0
- data/lib/rigor/language_server/publish_batcher.rb +82 -0
- data/lib/rigor/language_server/server.rb +38 -9
- data/lib/rigor/language_server.rb +2 -0
- data/lib/rigor/plugin/base.rb +29 -2
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/protection/closure_kill_oracle.rb +171 -0
- data/lib/rigor/protection/dependency_closure.rb +59 -0
- data/lib/rigor/protection/diagnostic_oracle.rb +12 -8
- data/lib/rigor/protection/discovery_seed.rb +116 -0
- data/lib/rigor/protection/kill_signature.rb +31 -0
- data/lib/rigor/protection/mutation_cache.rb +355 -0
- data/lib/rigor/protection/mutation_scanner.rb +55 -11
- data/lib/rigor/protection/mutator.rb +26 -4
- data/lib/rigor/runtime/jit.rb +63 -1
- data/lib/rigor/scope/discovery_index.rb +9 -0
- data/lib/rigor/scope.rb +52 -8
- data/lib/rigor/sig_gen/generator.rb +130 -31
- data/lib/rigor/sig_gen/layout_index.rb +6 -0
- data/lib/rigor/sig_gen/meta_class_shape.rb +84 -0
- data/lib/rigor/sig_gen/renderer.rb +20 -1
- data/lib/rigor/sig_gen/write_result.rb +6 -4
- data/lib/rigor/sig_gen/writer.rb +195 -47
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +51 -1
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +6 -6
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/result_shape.rb +146 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +177 -12
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +74 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +257 -0
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +69 -0
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +87 -11
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +34 -4
- data/sig/rigor/cache.rbs +6 -0
- data/sig/rigor/inference/void_origin.rbs +21 -0
- data/sig/rigor/plugin/base.rbs +4 -3
- data/sig/rigor/scope.rbs +8 -0
- metadata +23 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class DryValidation < Rigor::Plugin::Base
|
|
8
|
+
# Issue #137 slices 2/3 — recognises the `<Const>.new.call(...).to_h` call chain and builds the
|
|
9
|
+
# per-contract `HashShape` from the entry {ContractScanner.scan_schema_blocks} collected.
|
|
10
|
+
module ParamsShape
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# The Contract constant `to_h_node`'s receiver chain, or nil when the chain isn't the recognised
|
|
14
|
+
# `<Const>.new.call(...).to_h` form.
|
|
15
|
+
#
|
|
16
|
+
# Mirrors rigor-dry-schema's `ResultShape.schema_name` with one extra hop: a schema's `.call` is a
|
|
17
|
+
# bare class method on the schema constant itself, but a Contract's `.call` is an INSTANCE method
|
|
18
|
+
# (`NewUserContract.new.call(input)`), so the chain has to walk through `.new` first. Floor,
|
|
19
|
+
# matching dry-schema's own posture: the contract must be named by a constant as written at the
|
|
20
|
+
# call site. Reached through a local (`c = NewUserContract.new; c.call(x).to_h`) or a relative
|
|
21
|
+
# constant path, the chain contributes nothing and `to_h` types per the RBS overlay's generic
|
|
22
|
+
# `Hash[Symbol, untyped]` as it did before.
|
|
23
|
+
def contract_name(to_h_node)
|
|
24
|
+
return nil unless to_h_node.is_a?(Prism::CallNode) && to_h_node.name == :to_h
|
|
25
|
+
return nil unless to_h_node.arguments.nil? && to_h_node.block.nil?
|
|
26
|
+
|
|
27
|
+
call_node = to_h_node.receiver
|
|
28
|
+
return nil unless call_node.is_a?(Prism::CallNode) && call_node.name == :call
|
|
29
|
+
|
|
30
|
+
new_node = call_node.receiver
|
|
31
|
+
return nil unless new_node.is_a?(Prism::CallNode) && new_node.name == :new
|
|
32
|
+
return nil unless new_node.arguments.nil? && new_node.block.nil?
|
|
33
|
+
|
|
34
|
+
constant_name(new_node.receiver)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Delegates the entry -> HashShape build to rigor-dry-schema's own
|
|
38
|
+
# {DrySchema::ResultShape.build} — the identical algorithm the entry was collected with (via
|
|
39
|
+
# {ContractScanner.scan_schema_blocks}, which itself delegates to
|
|
40
|
+
# {DrySchema::SchemaScanner.collect_schema_shape}). Only ever called once the caller has already
|
|
41
|
+
# confirmed rigor-dry-schema is loaded.
|
|
42
|
+
def build(entry)
|
|
43
|
+
DrySchema::ResultShape.build(entry)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Renders `Foo` / `Foo::Bar` / `::Foo::Bar` as the `::`-joined String {ContractScanner}'s table is
|
|
47
|
+
# keyed by. Duplicated from rigor-dry-schema's identical helper rather than shared — five lines,
|
|
48
|
+
# no dependency on anything ELSE in that plugin, and reusing it would need a `registered_for`
|
|
49
|
+
# guard here too even though this leaf never touches dry-schema's own type/alias machinery.
|
|
50
|
+
def constant_name(node)
|
|
51
|
+
case node
|
|
52
|
+
when Prism::ConstantReadNode then node.name.to_s
|
|
53
|
+
when Prism::ConstantPathNode then constant_path_name(node)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def constant_path_name(node)
|
|
58
|
+
parent = node.parent
|
|
59
|
+
name = node.name&.to_s
|
|
60
|
+
return nil if name.nil?
|
|
61
|
+
return name if parent.nil? # `::Foo` — the table keys are unrooted, so drop the leading `::`
|
|
62
|
+
|
|
63
|
+
prefix = constant_name(parent)
|
|
64
|
+
prefix.nil? ? nil : "#{prefix}::#{name}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -5,6 +5,7 @@ require "prism"
|
|
|
5
5
|
require "rigor/plugin"
|
|
6
6
|
|
|
7
7
|
require_relative "dry_validation/contract_scanner"
|
|
8
|
+
require_relative "dry_validation/params_shape"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
10
11
|
module Plugin
|
|
@@ -21,13 +22,21 @@ module Rigor
|
|
|
21
22
|
# Result) and `Dry::Validation::Result#{success?, failure?, to_h}`. The manifest's
|
|
22
23
|
# `signature_paths: ["sig"]` auto-contributes the overlay (ADR-25) — no project-side wiring needed.
|
|
23
24
|
#
|
|
24
|
-
#
|
|
25
|
+
# Landed since (issue #137):
|
|
25
26
|
#
|
|
26
|
-
# -
|
|
27
|
-
#
|
|
28
|
-
# `
|
|
27
|
+
# - **Slice 2** — when `rigor-dry-schema` is loaded, a Contract's `params { ... }` block (the SAME
|
|
28
|
+
# dry-schema DSL a top-level `Dry::Schema.X { ... }` body uses) refines
|
|
29
|
+
# `NewUserContract.new.call(input).to_h` from the RBS overlay's generic `Hash[Symbol, untyped]` to
|
|
30
|
+
# the schema-typed `HashShape`. See {ContractScanner.scan_schema_blocks} + {ParamsShape}.
|
|
31
|
+
# - **Slice 3** — `json { ... }` adapter parity with `params { ... }`. Same mechanism, same fact.
|
|
32
|
+
# - **`dry-validation.rule-key-mismatch`** — a `rule(:key)` referencing a key absent from the
|
|
33
|
+
# Contract's own `params`/`json` block. Hard-gated: see {ContractScanner.rule_key_issues}'s doc for
|
|
34
|
+
# the two all-or-nothing checks that keep it silent on anything not statically resolvable.
|
|
29
35
|
#
|
|
30
|
-
#
|
|
36
|
+
# Without `rigor-dry-schema` loaded, `result.to_h` still types per the RBS overlay's generic
|
|
37
|
+
# `Hash[Symbol, untyped]` and `rule-key-mismatch` never fires — this plugin ships no required/optional
|
|
38
|
+
# walker of its own; it delegates to dry-schema's (see {ContractScanner}'s module doc for why that is
|
|
39
|
+
# a deliberate hard precondition, not a degrade-gracefully fallback).
|
|
31
40
|
#
|
|
32
41
|
# No ADR-3 amendment is needed for the validation surface itself; `Dry::Validation::Result` is a
|
|
33
42
|
# generic class, not a sum type (the `success?` / `failure?` predicates narrow via existing bool flow
|
|
@@ -36,31 +45,98 @@ module Rigor
|
|
|
36
45
|
manifest(
|
|
37
46
|
id: "dry-validation",
|
|
38
47
|
version: "0.1.0",
|
|
39
|
-
description: "Recognises `class T < Dry::Validation::Contract` subclasses
|
|
40
|
-
"
|
|
41
|
-
|
|
48
|
+
description: "Recognises `class T < Dry::Validation::Contract` subclasses, publishes the " \
|
|
49
|
+
"contract FQN set, and (with rigor-dry-schema loaded) refines each contract's " \
|
|
50
|
+
"`result.to_h` to its `params`/`json` schema shape.",
|
|
51
|
+
produces: %i[dry_validation_contracts dry_validation_params],
|
|
52
|
+
consumes: [{ plugin_id: "dry-types", name: :dry_type_aliases, optional: true }],
|
|
42
53
|
# Auto-contribute the bundled RBS overlay (Contract#call -> Result, Result#success?/#to_h/...)
|
|
43
54
|
# per ADR-25, so no project-side signature_paths wiring is needed.
|
|
44
55
|
signature_paths: ["sig"]
|
|
45
56
|
)
|
|
46
57
|
|
|
58
|
+
# ADR-37 dynamic return — refines `NewUserContract.new.call(input).to_h` to the per-contract
|
|
59
|
+
# `HashShape` when {#result_shape_for} finds one. Gated on the method name alone (mirrors
|
|
60
|
+
# rigor-dry-schema's own `.to_h` rule): the RBS overlay types `Contract#call`'s return as the
|
|
61
|
+
# generic `Result`, but that alone can't select a PER-CONTRACT shape, so the block does its own
|
|
62
|
+
# syntactic chain match via {ParamsShape.contract_name} before touching the table. When
|
|
63
|
+
# rigor-dry-schema isn't loaded, or the chain isn't a Contract call, this returns nil and the RBS
|
|
64
|
+
# overlay's generic `Hash[Symbol, untyped]` stands.
|
|
65
|
+
dynamic_return methods: [:to_h] do |call_node, _scope|
|
|
66
|
+
result_shape_for(call_node)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# ADR-37 slice 1c two-pass — the "collect" half of `dry-validation.rule-key-mismatch`. Reads the
|
|
70
|
+
# SAME `:dry_type_aliases` fact `#prepare` does (a plain `services.fact_store.read`, not
|
|
71
|
+
# `#read_fact`'s memoised wrapper — `services` at collect time is the run-wide instance, so an
|
|
72
|
+
# in-block memo isn't needed here the way it is across many per-call `dynamic_return` invocations).
|
|
73
|
+
node_file_context do |root, _scope|
|
|
74
|
+
type_aliases = services.fact_store.read(plugin_id: "dry-types", name: :dry_type_aliases) || {}
|
|
75
|
+
ContractScanner.rule_key_issues(root, type_aliases)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Fires once per file (see rigor-dry-schema's identical `Prism::ProgramNode` rule for why).
|
|
79
|
+
node_rule Prism::ProgramNode do |_node, _scope, path, issues|
|
|
80
|
+
next [] if issues.nil? || issues.empty?
|
|
81
|
+
|
|
82
|
+
issues.map do |issue|
|
|
83
|
+
hint = Rigor::Plugin::Base.suggest(issue[:key], issue[:known_keys])
|
|
84
|
+
suffix = hint ? " (did you mean `:#{hint}`?)" : ""
|
|
85
|
+
diagnostic(
|
|
86
|
+
issue[:node],
|
|
87
|
+
path: path,
|
|
88
|
+
message: "rule(:#{issue[:key]}) references a key not declared by " \
|
|
89
|
+
"`#{issue[:contract_fqn]}`'s params/json schema#{suffix}",
|
|
90
|
+
severity: :error,
|
|
91
|
+
rule: "dry-validation.rule-key-mismatch"
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
47
96
|
def prepare(services)
|
|
48
97
|
contracts = ContractScanner.scan(paths: scannable_paths(services))
|
|
49
|
-
|
|
98
|
+
unless contracts.empty?
|
|
99
|
+
services.fact_store.publish(
|
|
100
|
+
plugin_id: manifest.id,
|
|
101
|
+
name: :dry_validation_contracts,
|
|
102
|
+
value: contracts
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
type_aliases = services.fact_store.read(plugin_id: "dry-types", name: :dry_type_aliases) || {}
|
|
107
|
+
params_table = ContractScanner.scan_schema_blocks(paths: scannable_paths(services), type_aliases: type_aliases)
|
|
108
|
+
return if params_table.empty?
|
|
50
109
|
|
|
51
110
|
services.fact_store.publish(
|
|
52
111
|
plugin_id: manifest.id,
|
|
53
|
-
name: :
|
|
54
|
-
value:
|
|
112
|
+
name: :dry_validation_params,
|
|
113
|
+
value: params_table
|
|
55
114
|
)
|
|
56
115
|
end
|
|
57
116
|
|
|
58
117
|
def init(_services)
|
|
59
118
|
@scannable_paths = nil
|
|
119
|
+
@params_shapes = {}
|
|
60
120
|
end
|
|
61
121
|
|
|
62
122
|
private
|
|
63
123
|
|
|
124
|
+
# The HashShape for a `<Const>.new.call(...).to_h` chain, or nil when the chain isn't that shape,
|
|
125
|
+
# names no contract in the table, or the contract's `params`/`json` shape declares no key.
|
|
126
|
+
# `params` wins over `json` when (unusually) a Contract declares both — mirrors dry-schema's own
|
|
127
|
+
# per-name memoisation, including nil, for the same reason: the miss is the common case.
|
|
128
|
+
def result_shape_for(call_node)
|
|
129
|
+
name = ParamsShape.contract_name(call_node)
|
|
130
|
+
return nil if name.nil?
|
|
131
|
+
|
|
132
|
+
shapes = (@params_shapes ||= {})
|
|
133
|
+
return shapes[name] if shapes.key?(name)
|
|
134
|
+
|
|
135
|
+
entry = read_fact(plugin_id: manifest.id, name: :dry_validation_params)&.[](name)
|
|
136
|
+
row = entry && (entry[:params] || entry[:json])
|
|
137
|
+
shapes[name] = row.nil? ? nil : ParamsShape.build(row)
|
|
138
|
+
end
|
|
139
|
+
|
|
64
140
|
def scannable_paths(services)
|
|
65
141
|
@scannable_paths ||= services.configuration.paths.flat_map do |entry|
|
|
66
142
|
if File.directory?(entry)
|
|
@@ -60,9 +60,11 @@ module Rigor
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# Return value contract:
|
|
63
|
-
# - `String` (non-empty)
|
|
64
|
-
# - `nil`
|
|
65
|
-
# - `[:error, message_string]`
|
|
63
|
+
# - `String` (non-empty) → successful synthesis
|
|
64
|
+
# - `nil` → no contribution
|
|
65
|
+
# - `[:error, message_string]` → parse failed, surface info diagnostic per ADR-32 WD6
|
|
66
|
+
# - `[:ok, source, [message, …]]` → synthesis succeeded, but an annotation was parsed and NOT
|
|
67
|
+
# honoured; surface info diagnostics per ADR-32 WD12
|
|
66
68
|
def call(source_file_path)
|
|
67
69
|
return nil unless RBS_INLINE_AVAILABLE
|
|
68
70
|
return nil unless File.file?(source_file_path)
|
|
@@ -83,7 +85,8 @@ module Rigor
|
|
|
83
85
|
rendered = ::RBS::Inline::Writer.write(uses, decls, rbs_decls)
|
|
84
86
|
return nil if rendered.nil? || rendered.strip.empty?
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
notices = unhonoured_annotations(result)
|
|
89
|
+
notices.empty? ? rendered : [:ok, rendered, notices]
|
|
87
90
|
rescue ::StandardError => e
|
|
88
91
|
# WD6 fail-soft — surface a structured error tuple so the engine's `Environment.for_project` can
|
|
89
92
|
# emit a `source-rbs-synthesis-failed` info diagnostic naming the file + the upstream error
|
|
@@ -113,6 +116,33 @@ module Rigor
|
|
|
113
116
|
.any? { |parsed| parsed.each_annotation.any? }
|
|
114
117
|
end
|
|
115
118
|
|
|
119
|
+
# ADR-32 WD12 — annotations upstream's parser ACCEPTS and its writer then contributes nothing from.
|
|
120
|
+
# These are invisible without a report: synthesis succeeds, and the annotation comment is even echoed
|
|
121
|
+
# into the generated RBS, so the omission shows up neither in the output nor at runtime.
|
|
122
|
+
#
|
|
123
|
+
# The one case today is `module-self`, where the two inline-RBS dialects disagree on spelling. rbs's
|
|
124
|
+
# own `docs/inline.md` documents `# @rbs module-self: Foo`; the rbs-inline gem's grammar is
|
|
125
|
+
# `# @rbs module-self Foo`, without the colon. Handed the colon form the gem still builds a
|
|
126
|
+
# `ModuleSelf` annotation but extracts no types from it, so an empty `self_types` on a parsed
|
|
127
|
+
# annotation is a precise signature for "the author asked for a constraint we did not apply".
|
|
128
|
+
# Measured both ways in `docs/notes/20260730-inline-rbs-parser-grammar-diff.md`.
|
|
129
|
+
#
|
|
130
|
+
# Deliberately narrow. A construct the gem's parser REJECTS already routes through WD6's error path,
|
|
131
|
+
# and one it never recognised at all is upstream's grammar to define (WD3) — guessing at those would
|
|
132
|
+
# make this a lint on comment prose, which is exactly the false-positive cost ADR-5 ranks first.
|
|
133
|
+
def unhonoured_annotations(prism_result)
|
|
134
|
+
::RBS::Inline::AnnotationParser.parse(prism_result.comments).flat_map do |parsed|
|
|
135
|
+
parsed.each_annotation.filter_map do |annotation|
|
|
136
|
+
next unless annotation.is_a?(::RBS::Inline::AST::Annotations::ModuleSelf)
|
|
137
|
+
next unless annotation.self_types.empty?
|
|
138
|
+
|
|
139
|
+
"`@rbs module-self` contributed no self-type constraint. Rigor reads the " \
|
|
140
|
+
"`# @rbs module-self Foo` spelling; `# @rbs module-self: Foo` (the spelling in rbs's own " \
|
|
141
|
+
"inline documentation) is not honoured here."
|
|
142
|
+
end
|
|
143
|
+
end.uniq
|
|
144
|
+
end
|
|
145
|
+
|
|
116
146
|
# Rewrite every RDoc directive comment to its spaced spelling (`#:nodoc:` -> `# :nodoc:`) so
|
|
117
147
|
# upstream's annotation grammar never sees it, and re-parse. Two reasons this happens here rather
|
|
118
148
|
# than in {#annotated?}: the directive must not gate a file in, AND it must not reach the synthesis,
|
data/sig/rigor/cache.rbs
CHANGED
|
@@ -14,6 +14,12 @@ module Rigor
|
|
|
14
14
|
# PRODUCER_ID constant and a private `self.compute(loader)`.
|
|
15
15
|
class RbsCacheProducer
|
|
16
16
|
def self.fetch: (loader: untyped, store: untyped) -> untyped
|
|
17
|
+
|
|
18
|
+
# The generation cap this producer declares to `Cache::Store#evict!`
|
|
19
|
+
# (issue #151). `rigor sig-gen` infers the literal `2` from the base
|
|
20
|
+
# body; the contract is the wider `Integer`, since a subclass may
|
|
21
|
+
# override it.
|
|
22
|
+
def self.generation_cap: () -> Integer
|
|
17
23
|
end
|
|
18
24
|
end
|
|
19
25
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Rigor
|
|
2
|
+
module Inference
|
|
3
|
+
# `VoidOrigin = Data.define(:class_name, :method_name, :kind)`. Declared as a `::Data` subclass because that
|
|
4
|
+
# is the shape the runtime actually builds. `sig-gen` reads this form since #227 and generates the same
|
|
5
|
+
# structure; the member types are hand-narrowed from the `untyped` only `--params=observed` could improve on.
|
|
6
|
+
class VoidOrigin < ::Data
|
|
7
|
+
attr_reader class_name: String
|
|
8
|
+
attr_reader method_name: Symbol
|
|
9
|
+
attr_reader kind: Symbol
|
|
10
|
+
|
|
11
|
+
def self.new: (class_name: String, method_name: Symbol, kind: Symbol) -> instance
|
|
12
|
+
| (String class_name, Symbol method_name, Symbol kind) -> instance
|
|
13
|
+
|
|
14
|
+
def self.[]: (class_name: String, method_name: Symbol, kind: Symbol) -> instance
|
|
15
|
+
| (String class_name, Symbol method_name, Symbol kind) -> instance
|
|
16
|
+
|
|
17
|
+
# A human-facing `Class#method` / `Class.method` label for the diagnostic message.
|
|
18
|
+
def label: () -> String
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/sig/rigor/plugin/base.rbs
CHANGED
|
@@ -14,7 +14,7 @@ class Rigor::Plugin::Base
|
|
|
14
14
|
# the no-arg `manifest` reads the cached value back.
|
|
15
15
|
def self.manifest: (**untyped fields) -> Rigor::Plugin::Manifest
|
|
16
16
|
|
|
17
|
-
def self.producer: (untyped id, ?watch: untyped, ?serialize: untyped, ?deserialize: untyped) { (untyped params) -> untyped } -> Symbol
|
|
17
|
+
def self.producer: (untyped id, ?watch: untyped, ?serialize: untyped, ?deserialize: untyped, ?generation_cap: untyped) { (untyped params) -> untyped } -> Symbol
|
|
18
18
|
def self.producers: () -> Hash[Symbol, untyped]
|
|
19
19
|
|
|
20
20
|
def self.node_rule: (untyped node_type) { (*untyped) -> untyped } -> untyped
|
|
@@ -23,11 +23,12 @@ class Rigor::Plugin::Base
|
|
|
23
23
|
def self.node_file_context: () { (untyped root, untyped scope) -> untyped } -> untyped
|
|
24
24
|
def self.node_file_context_block: () -> untyped
|
|
25
25
|
|
|
26
|
-
def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
26
|
+
def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?, ?file_methods: (^(untyped path) -> Array[untyped])?) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
27
27
|
def self.dynamic_returns: () -> Array[untyped]
|
|
28
28
|
|
|
29
29
|
def self.narrowing_facts: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
30
|
-
#
|
|
30
|
+
# The engine-facing reader for the declared `narrowing_facts` rules. Named
|
|
31
|
+
# `type_specifiers` before ADR-80; that spelling was removed in 0.3.0.
|
|
31
32
|
def self.narrowing_facts_rules: () -> Array[untyped]
|
|
32
33
|
|
|
33
34
|
def self.suggest: (untyped name, untyped candidates) -> String?
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -12,6 +12,9 @@ module Rigor
|
|
|
12
12
|
attr_reader local_origins: Hash[Symbol, Symbol]
|
|
13
13
|
attr_reader ivar_origins: Hash[Symbol, Symbol]
|
|
14
14
|
attr_reader void_origins: Hash[untyped, Inference::VoidOrigin]
|
|
15
|
+
attr_reader optimistic_origins: Hash[untyped, Symbol]
|
|
16
|
+
attr_reader optimistic_locals: Hash[Symbol, Symbol]
|
|
17
|
+
attr_reader optimistic_ivars: Hash[Symbol, Symbol]
|
|
15
18
|
attr_reader indexed_narrowings: Hash[IndexedKey, Type::t]
|
|
16
19
|
attr_reader method_chain_narrowings: Hash[ChainKey, Type::t]
|
|
17
20
|
attr_reader source_path: String?
|
|
@@ -39,6 +42,7 @@ module Rigor
|
|
|
39
42
|
def param_inferred_types: () -> Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
40
43
|
def run_generation: () -> Object?
|
|
41
44
|
def record_dynamic_origin: (untyped node, Symbol cause) -> Scope
|
|
45
|
+
def record_optimistic_origin: (untyped node, Symbol cause) -> Scope
|
|
42
46
|
|
|
43
47
|
def record_void_origin: (untyped node, Inference::VoidOrigin origin) -> Scope
|
|
44
48
|
|
|
@@ -93,9 +97,13 @@ module Rigor
|
|
|
93
97
|
def global: (String | Symbol name) -> Type::t?
|
|
94
98
|
def local_origin: (String | Symbol name) -> Symbol?
|
|
95
99
|
def ivar_origin: (String | Symbol name) -> Symbol?
|
|
100
|
+
def optimistic_local: (String | Symbol name) -> Symbol?
|
|
101
|
+
def optimistic_ivar: (String | Symbol name) -> Symbol?
|
|
96
102
|
def with_local: (String | Symbol name, Type::t type) -> Scope
|
|
97
103
|
def with_ivar: (String | Symbol name, Type::t type) -> Scope
|
|
98
104
|
def with_local_origin: (String | Symbol name, Symbol? cause) -> Scope
|
|
105
|
+
def with_optimistic_local: (String | Symbol name, Symbol? cause) -> Scope
|
|
106
|
+
def with_optimistic_ivar: (String | Symbol name, Symbol? cause) -> Scope
|
|
99
107
|
def with_ivar_origin: (String | Symbol name, Symbol? cause) -> Scope
|
|
100
108
|
def with_cvar: (String | Symbol name, Type::t type) -> Scope
|
|
101
109
|
def with_global: (String | Symbol name, Type::t type) -> Scope
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rigortype
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rigor contributors
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 1980-01-
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: language_server-protocol
|
|
@@ -290,6 +290,7 @@ files:
|
|
|
290
290
|
- data/core_overlay/numeric.rbs
|
|
291
291
|
- data/core_overlay/pathname.rbs
|
|
292
292
|
- data/core_overlay/psych.rbs
|
|
293
|
+
- data/core_overlay/resolv.rbs
|
|
293
294
|
- data/core_overlay/string_scanner.rbs
|
|
294
295
|
- data/gem_overlay/activesupport/core_ext.rbs
|
|
295
296
|
- data/vendored_gem_sigs/ast/ast.rbs
|
|
@@ -306,6 +307,7 @@ files:
|
|
|
306
307
|
- data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs
|
|
307
308
|
- data/vendored_gem_sigs/pg/pg.rbs
|
|
308
309
|
- data/vendored_gem_sigs/prism/prism_supplement.rbs
|
|
310
|
+
- data/vendored_gem_sigs/racc/racc.rbs
|
|
309
311
|
- data/vendored_gem_sigs/redis/errors.rbs
|
|
310
312
|
- data/vendored_gem_sigs/redis/future.rbs
|
|
311
313
|
- data/vendored_gem_sigs/redis/redis.rbs
|
|
@@ -428,6 +430,7 @@ files:
|
|
|
428
430
|
- lib/rigor/analysis/run_cache_probe.rb
|
|
429
431
|
- lib/rigor/analysis/run_stats.rb
|
|
430
432
|
- lib/rigor/analysis/runner.rb
|
|
433
|
+
- lib/rigor/analysis/runner/buffer_pool_dispatcher.rb
|
|
431
434
|
- lib/rigor/analysis/runner/diagnostic_aggregator.rb
|
|
432
435
|
- lib/rigor/analysis/runner/pool_coordinator.rb
|
|
433
436
|
- lib/rigor/analysis/runner/project_pre_passes.rb
|
|
@@ -444,6 +447,7 @@ files:
|
|
|
444
447
|
- lib/rigor/builtins/regex_refinement.rb
|
|
445
448
|
- lib/rigor/builtins/static_return_refinements.rb
|
|
446
449
|
- lib/rigor/cache/descriptor.rb
|
|
450
|
+
- lib/rigor/cache/engine_source.rb
|
|
447
451
|
- lib/rigor/cache/file_digest.rb
|
|
448
452
|
- lib/rigor/cache/incremental_snapshot.rb
|
|
449
453
|
- lib/rigor/cache/rbs_cache_producer.rb
|
|
@@ -460,6 +464,7 @@ files:
|
|
|
460
464
|
- lib/rigor/cli/annotate_command.rb
|
|
461
465
|
- lib/rigor/cli/baseline_command.rb
|
|
462
466
|
- lib/rigor/cli/check_command.rb
|
|
467
|
+
- lib/rigor/cli/check_invocation.rb
|
|
463
468
|
- lib/rigor/cli/check_runner_factory.rb
|
|
464
469
|
- lib/rigor/cli/command.rb
|
|
465
470
|
- lib/rigor/cli/coverage_command.rb
|
|
@@ -476,6 +481,7 @@ files:
|
|
|
476
481
|
- lib/rigor/cli/fused_protection_report.rb
|
|
477
482
|
- lib/rigor/cli/lsp_command.rb
|
|
478
483
|
- lib/rigor/cli/mcp_command.rb
|
|
484
|
+
- lib/rigor/cli/mutation_fork_scan.rb
|
|
479
485
|
- lib/rigor/cli/mutation_protection_renderer.rb
|
|
480
486
|
- lib/rigor/cli/mutation_protection_report.rb
|
|
481
487
|
- lib/rigor/cli/options.rb
|
|
@@ -491,6 +497,7 @@ files:
|
|
|
491
497
|
- lib/rigor/cli/show_bleedingedge_command.rb
|
|
492
498
|
- lib/rigor/cli/sig_gen_command.rb
|
|
493
499
|
- lib/rigor/cli/skill_command.rb
|
|
500
|
+
- lib/rigor/cli/skill_deep_probe.rb
|
|
494
501
|
- lib/rigor/cli/skill_describe.rb
|
|
495
502
|
- lib/rigor/cli/trace_command.rb
|
|
496
503
|
- lib/rigor/cli/trace_renderer.rb
|
|
@@ -597,6 +604,7 @@ files:
|
|
|
597
604
|
- lib/rigor/inference/multi_target_binder.rb
|
|
598
605
|
- lib/rigor/inference/mutation_widening.rb
|
|
599
606
|
- lib/rigor/inference/narrowing.rb
|
|
607
|
+
- lib/rigor/inference/optimistic_origin.rb
|
|
600
608
|
- lib/rigor/inference/origin_lookup.rb
|
|
601
609
|
- lib/rigor/inference/parameter_inference_collector.rb
|
|
602
610
|
- lib/rigor/inference/precision_scanner.rb
|
|
@@ -604,6 +612,7 @@ files:
|
|
|
604
612
|
- lib/rigor/inference/project_patched_scanner.rb
|
|
605
613
|
- lib/rigor/inference/protection_scanner.rb
|
|
606
614
|
- lib/rigor/inference/rbs_type_translator.rb
|
|
615
|
+
- lib/rigor/inference/receiver_alias.rb
|
|
607
616
|
- lib/rigor/inference/scope_indexer.rb
|
|
608
617
|
- lib/rigor/inference/statement_evaluator.rb
|
|
609
618
|
- lib/rigor/inference/struct_fold_safety.rb
|
|
@@ -622,8 +631,10 @@ files:
|
|
|
622
631
|
- lib/rigor/language_server/folding_range_provider.rb
|
|
623
632
|
- lib/rigor/language_server/hover_provider.rb
|
|
624
633
|
- lib/rigor/language_server/hover_renderer.rb
|
|
634
|
+
- lib/rigor/language_server/incremental_sync.rb
|
|
625
635
|
- lib/rigor/language_server/loop.rb
|
|
626
636
|
- lib/rigor/language_server/project_context.rb
|
|
637
|
+
- lib/rigor/language_server/publish_batcher.rb
|
|
627
638
|
- lib/rigor/language_server/selection_range_provider.rb
|
|
628
639
|
- lib/rigor/language_server/server.rb
|
|
629
640
|
- lib/rigor/language_server/signature_help_provider.rb
|
|
@@ -658,7 +669,12 @@ files:
|
|
|
658
669
|
- lib/rigor/plugin/source_rbs_synthesis_reporter.rb
|
|
659
670
|
- lib/rigor/plugin/trust_policy.rb
|
|
660
671
|
- lib/rigor/plugin/type_node_resolver.rb
|
|
672
|
+
- lib/rigor/protection/closure_kill_oracle.rb
|
|
673
|
+
- lib/rigor/protection/dependency_closure.rb
|
|
661
674
|
- lib/rigor/protection/diagnostic_oracle.rb
|
|
675
|
+
- lib/rigor/protection/discovery_seed.rb
|
|
676
|
+
- lib/rigor/protection/kill_signature.rb
|
|
677
|
+
- lib/rigor/protection/mutation_cache.rb
|
|
662
678
|
- lib/rigor/protection/mutation_scanner.rb
|
|
663
679
|
- lib/rigor/protection/mutator.rb
|
|
664
680
|
- lib/rigor/protection/test_suite_oracle.rb
|
|
@@ -674,6 +690,7 @@ files:
|
|
|
674
690
|
- lib/rigor/sig_gen/classification.rb
|
|
675
691
|
- lib/rigor/sig_gen/generator.rb
|
|
676
692
|
- lib/rigor/sig_gen/layout_index.rb
|
|
693
|
+
- lib/rigor/sig_gen/meta_class_shape.rb
|
|
677
694
|
- lib/rigor/sig_gen/method_candidate.rb
|
|
678
695
|
- lib/rigor/sig_gen/observation_collector.rb
|
|
679
696
|
- lib/rigor/sig_gen/observed_call.rb
|
|
@@ -773,6 +790,7 @@ files:
|
|
|
773
790
|
- plugins/rigor-devise/lib/rigor/plugin/devise.rb
|
|
774
791
|
- plugins/rigor-dry-schema/lib/rigor-dry-schema.rb
|
|
775
792
|
- plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb
|
|
793
|
+
- plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/result_shape.rb
|
|
776
794
|
- plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb
|
|
777
795
|
- plugins/rigor-dry-struct/lib/rigor-dry-struct.rb
|
|
778
796
|
- plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb
|
|
@@ -782,6 +800,7 @@ files:
|
|
|
782
800
|
- plugins/rigor-dry-validation/lib/rigor-dry-validation.rb
|
|
783
801
|
- plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb
|
|
784
802
|
- plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb
|
|
803
|
+
- plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb
|
|
785
804
|
- plugins/rigor-dry-validation/sig/dry_validation.rbs
|
|
786
805
|
- plugins/rigor-factorybot/lib/rigor-factorybot.rb
|
|
787
806
|
- plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb
|
|
@@ -876,6 +895,7 @@ files:
|
|
|
876
895
|
- sig/rigor/inference/builtins.rbs
|
|
877
896
|
- sig/rigor/inference/builtins/method_catalog.rbs
|
|
878
897
|
- sig/rigor/inference/builtins/numeric_catalog.rbs
|
|
898
|
+
- sig/rigor/inference/void_origin.rbs
|
|
879
899
|
- sig/rigor/plugin.rbs
|
|
880
900
|
- sig/rigor/plugin/access_denied_error.rbs
|
|
881
901
|
- sig/rigor/plugin/base.rbs
|
|
@@ -987,7 +1007,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
987
1007
|
- !ruby/object:Gem::Version
|
|
988
1008
|
version: '0'
|
|
989
1009
|
requirements: []
|
|
990
|
-
rubygems_version:
|
|
1010
|
+
rubygems_version: 4.0.10
|
|
991
1011
|
specification_version: 4
|
|
992
1012
|
summary: Inference-first static analysis for Ruby.
|
|
993
1013
|
test_files: []
|