rigortype 0.1.18 → 0.2.0
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 +159 -224
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +32 -23
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +151 -23
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules.rb +756 -132
- data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
- data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
- data/lib/rigor/analysis/diagnostic.rb +8 -0
- data/lib/rigor/analysis/fact_store.rb +5 -4
- data/lib/rigor/analysis/rule_catalog.rb +153 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +19 -18
- data/lib/rigor/analysis/runner/project_pre_passes.rb +13 -9
- data/lib/rigor/analysis/runner.rb +75 -27
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +31 -25
- data/lib/rigor/bleeding_edge.rb +123 -0
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/descriptor.rb +86 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +122 -16
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +118 -16
- data/lib/rigor/cli/coverage_command.rb +148 -16
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -0
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
- data/lib/rigor/cli/mutation_protection_report.rb +73 -0
- data/lib/rigor/cli/options.rb +9 -0
- data/lib/rigor/cli/plugins_command.rb +4 -5
- data/lib/rigor/cli/plugins_renderer.rb +0 -2
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +2 -1
- data/lib/rigor/cli/triage_command.rb +8 -4
- data/lib/rigor/cli/triage_renderer.rb +15 -1
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_command.rb +2 -1
- data/lib/rigor/cli.rb +12 -3
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration/severity_profile.rb +13 -1
- data/lib/rigor/configuration.rb +100 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
- data/lib/rigor/environment/class_registry.rb +4 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
- data/lib/rigor/environment/lockfile_resolver.rb +1 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
- data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
- data/lib/rigor/environment/rbs_loader.rb +74 -5
- data/lib/rigor/environment.rb +17 -7
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution.rb +3 -5
- data/lib/rigor/inference/acceptance.rb +17 -9
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +89 -0
- data/lib/rigor/inference/budget_trace.rb +29 -2
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
- data/lib/rigor/inference/expression_typer.rb +1072 -71
- data/lib/rigor/inference/hkt_body.rb +8 -11
- data/lib/rigor/inference/hkt_body_parser.rb +10 -12
- data/lib/rigor/inference/hkt_registry.rb +10 -11
- data/lib/rigor/inference/macro_block_self_type.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +210 -35
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +237 -24
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +112 -49
- data/lib/rigor/inference/method_parameter_binder.rb +56 -2
- data/lib/rigor/inference/multi_target_binder.rb +46 -3
- data/lib/rigor/inference/mutation_widening.rb +147 -11
- data/lib/rigor/inference/narrowing.rb +284 -53
- data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
- data/lib/rigor/inference/project_patched_methods.rb +4 -7
- data/lib/rigor/inference/project_patched_scanner.rb +2 -13
- data/lib/rigor/inference/protection_scanner.rb +86 -0
- data/lib/rigor/inference/scope_indexer.rb +821 -76
- data/lib/rigor/inference/statement_evaluator.rb +1179 -102
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
- data/lib/rigor/language_server/completion_provider.rb +6 -12
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
- data/lib/rigor/language_server/hover_provider.rb +2 -3
- data/lib/rigor/language_server/hover_renderer.rb +2 -11
- data/lib/rigor/language_server/server.rb +9 -17
- data/lib/rigor/language_server.rb +4 -5
- data/lib/rigor/plugin/base.rb +245 -87
- data/lib/rigor/plugin/macro/block_as_method.rb +25 -25
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +6 -8
- data/lib/rigor/plugin/manifest.rb +49 -90
- data/lib/rigor/plugin/node_rule_walk.rb +59 -14
- data/lib/rigor/plugin/registry.rb +18 -18
- data/lib/rigor/plugin/type_node_resolver.rb +6 -8
- data/lib/rigor/protection/mutation_scanner.rb +120 -0
- data/lib/rigor/protection/mutator.rb +246 -0
- data/lib/rigor/rbs_extended.rb +24 -36
- data/lib/rigor/reflection.rb +4 -7
- data/lib/rigor/scope/discovery_index.rb +16 -2
- data/lib/rigor/scope.rb +185 -16
- data/lib/rigor/sig_gen/generator.rb +8 -0
- data/lib/rigor/sig_gen/observed_call.rb +3 -3
- data/lib/rigor/sig_gen/writer.rb +40 -2
- data/lib/rigor/source/constant_path.rb +62 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +4 -19
- data/lib/rigor/triage.rb +69 -1
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +45 -3
- data/lib/rigor/type/constant.rb +2 -11
- data/lib/rigor/type/data_class.rb +2 -11
- data/lib/rigor/type/data_instance.rb +2 -11
- data/lib/rigor/type/hash_shape.rb +2 -11
- data/lib/rigor/type/integer_range.rb +2 -11
- data/lib/rigor/type/intersection.rb +2 -11
- data/lib/rigor/type/nominal.rb +2 -11
- data/lib/rigor/type/plain_lattice.rb +37 -0
- data/lib/rigor/type/refined.rb +72 -13
- data/lib/rigor/type/singleton.rb +2 -11
- data/lib/rigor/type/struct_class.rb +75 -0
- data/lib/rigor/type/struct_instance.rb +93 -0
- data/lib/rigor/type/tuple.rb +5 -15
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +16 -32
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +34 -100
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +26 -27
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +9 -8
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +18 -49
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +4 -4
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +22 -35
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +16 -23
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +21 -27
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +52 -40
- data/sig/rigor/analysis/fact_store.rbs +3 -0
- data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
- data/sig/rigor/plugin/base.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +1 -2
- data/sig/rigor/scope.rbs +18 -1
- data/sig/rigor/type.rbs +37 -1
- data/sig/rigor.rbs +1 -1
- data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
- data/skills/rigor-plugin-author/SKILL.md +6 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
- metadata +25 -2
- data/lib/rigor/plugin/macro/external_file.rb +0 -143
|
@@ -63,7 +63,7 @@ module Rigor
|
|
|
63
63
|
}
|
|
64
64
|
)
|
|
65
65
|
|
|
66
|
-
producer :worker_index do |_params|
|
|
66
|
+
producer :worker_index, watch: -> { [[@worker_search_paths, "**/*.rb"]] } do |_params|
|
|
67
67
|
WorkerDiscoverer.new(
|
|
68
68
|
io_boundary: io_boundary,
|
|
69
69
|
search_paths: @worker_search_paths,
|
|
@@ -74,46 +74,33 @@ module Rigor
|
|
|
74
74
|
def init(_services)
|
|
75
75
|
@worker_search_paths = Array(config.fetch("worker_search_paths")).map(&:to_s)
|
|
76
76
|
@worker_marker_modules = Array(config.fetch("worker_marker_modules")).map(&:to_s)
|
|
77
|
-
@worker_index = nil
|
|
78
|
-
@load_error = nil
|
|
79
77
|
end
|
|
80
78
|
|
|
81
79
|
# File-level only: the load-error emission. The per-call arity
|
|
82
80
|
# validation runs over the engine-owned walk via the node_rule
|
|
83
81
|
# below (ADR-37). The worker index is lazily loaded + memoised by
|
|
84
|
-
#
|
|
82
|
+
# `producer_value`, shared by both surfaces.
|
|
85
83
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
86
|
-
index =
|
|
87
|
-
return [load_error_diagnostic(path)] if index.nil? &&
|
|
84
|
+
index = producer_value(:worker_index)
|
|
85
|
+
return [load_error_diagnostic(path)] if index.nil? && producer_error(:worker_index)
|
|
88
86
|
|
|
89
87
|
[]
|
|
90
88
|
end
|
|
91
89
|
|
|
92
90
|
node_rule Prism::CallNode do |node, _scope, path|
|
|
93
|
-
index =
|
|
91
|
+
index = producer_value(:worker_index)
|
|
94
92
|
next [] if index.nil? || index.empty?
|
|
95
93
|
|
|
96
|
-
Analyzer.violations_for(call_node: node, worker_index: index)
|
|
97
|
-
diagnostic(node, path: path, message: violation.message, severity: violation.severity, rule: violation.rule)
|
|
98
|
-
end
|
|
94
|
+
diagnostics_for(Analyzer.violations_for(call_node: node, worker_index: index), path: path, node: node)
|
|
99
95
|
end
|
|
100
96
|
|
|
101
97
|
private
|
|
102
98
|
|
|
103
|
-
def worker_index_or_nil
|
|
104
|
-
return @worker_index if @worker_index
|
|
105
|
-
|
|
106
|
-
descriptor = glob_descriptor(@worker_search_paths, "**/*.rb")
|
|
107
|
-
@worker_index = cache_for(:worker_index, params: {}, descriptor: descriptor).call
|
|
108
|
-
rescue StandardError => e
|
|
109
|
-
@load_error = "rigor-sidekiq: failed to discover workers: #{e.class}: #{e.message}"
|
|
110
|
-
nil
|
|
111
|
-
end
|
|
112
|
-
|
|
113
99
|
def load_error_diagnostic(path)
|
|
100
|
+
error = producer_error(:worker_index)
|
|
114
101
|
Rigor::Analysis::Diagnostic.new(
|
|
115
102
|
path: path, line: 1, column: 1,
|
|
116
|
-
message:
|
|
103
|
+
message: "rigor-sidekiq: failed to discover workers: #{error.class}: #{error.message}",
|
|
117
104
|
severity: :warning,
|
|
118
105
|
rule: "load-error"
|
|
119
106
|
)
|
|
@@ -74,7 +74,7 @@ module Rigor
|
|
|
74
74
|
block_as_methods: [
|
|
75
75
|
Rigor::Plugin::Macro::BlockAsMethod.new(
|
|
76
76
|
receiver_constraint: "Sinatra::Base",
|
|
77
|
-
|
|
77
|
+
method_names: %i[get post put delete head options patch link unlink]
|
|
78
78
|
)
|
|
79
79
|
]
|
|
80
80
|
)
|
|
@@ -168,9 +168,8 @@ module Rigor
|
|
|
168
168
|
# contribution mirrors `T.must` minus the nil-stripping:
|
|
169
169
|
# the call's return type is the inner expression's
|
|
170
170
|
# inferred type. The companion diagnostic is emitted by
|
|
171
|
-
# the plugin's `diagnostics_for_file` hook
|
|
172
|
-
#
|
|
173
|
-
# contribution-only.
|
|
171
|
+
# the plugin's `diagnostics_for_file` hook; this
|
|
172
|
+
# method handles the contribution only.
|
|
174
173
|
def resolve_reveal_type(call_node, scope)
|
|
175
174
|
inner = nth_argument(call_node, 0)
|
|
176
175
|
return Rigor::Type::Combinator.untyped if inner.nil? || scope.nil?
|
|
@@ -4,22 +4,18 @@ module Rigor
|
|
|
4
4
|
module Plugin
|
|
5
5
|
class Sorbet < Rigor::Plugin::Base
|
|
6
6
|
# Frozen description of one Sorbet `sig` block as parsed by
|
|
7
|
-
# {SigParser}. Holds
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# compatibility).
|
|
7
|
+
# {SigParser}. Holds the return type, parameter shape, and
|
|
8
|
+
# modifier list. Call-site argument-type checking and
|
|
9
|
+
# override-compatibility validation are deferred; only the
|
|
10
|
+
# return-type contribution is active.
|
|
12
11
|
#
|
|
13
12
|
# `kind` distinguishes `def foo` (`:instance`) from
|
|
14
13
|
# `def self.foo` / `class << self; def foo; end`
|
|
15
14
|
# (`:singleton`).
|
|
16
15
|
#
|
|
17
|
-
# `modifiers`
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# later slices wire `:abstract` into the existing
|
|
21
|
-
# `def.return-type-mismatch` check and `:override` into
|
|
22
|
-
# override-compatibility validation.
|
|
16
|
+
# `modifiers` records the `sig`-level flags observed:
|
|
17
|
+
# `:abstract`, `:override`, `:overridable`, `:final`.
|
|
18
|
+
# Currently stored but not acted on.
|
|
23
19
|
MethodSignature = Data.define(
|
|
24
20
|
:class_name, :method_name, :kind, :params, :return_type, :modifiers
|
|
25
21
|
)
|
|
@@ -26,9 +26,8 @@ module Rigor
|
|
|
26
26
|
# whatever it recognises (`params` / `returns` / `void` /
|
|
27
27
|
# `abstract` / `override` / `overridable` / `final` /
|
|
28
28
|
# `type_parameters` / `checked` / `on_failure`) into a
|
|
29
|
-
# frozen result hash
|
|
30
|
-
#
|
|
31
|
-
# on the modifiers and `type_parameters`.
|
|
29
|
+
# frozen result hash stored in {MethodSignature}. Modifiers
|
|
30
|
+
# and `type_parameters` are recorded but not yet acted on.
|
|
32
31
|
#
|
|
33
32
|
# The parser is intentionally tolerant — unknown chain
|
|
34
33
|
# nodes degrade to "the rest of the chain is opaque" rather
|
|
@@ -93,8 +92,8 @@ module Rigor
|
|
|
93
92
|
when :params
|
|
94
93
|
accumulator[:params].merge!(parse_params(current))
|
|
95
94
|
when :type_parameters
|
|
96
|
-
#
|
|
97
|
-
#
|
|
95
|
+
# Recognised to suppress the degraded path;
|
|
96
|
+
# payload intentionally discarded (deferred).
|
|
98
97
|
when *RECOGNISED_MODIFIERS
|
|
99
98
|
accumulator[:modifiers] << current.name
|
|
100
99
|
when *RUNTIME_ONLY_STEPS
|
|
@@ -17,15 +17,12 @@ module Rigor
|
|
|
17
17
|
# default) when no sigil is present, matching how Sorbet
|
|
18
18
|
# treats sigil-less files.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# requires threading the file path through
|
|
27
|
-
# the per-call recognition path, which lives behind a
|
|
28
|
-
# future plugin-contract widening slice.
|
|
20
|
+
# ADR-11 slice 5 uses this at catalog-harvest time:
|
|
21
|
+
# `# typed: ignore` files are skipped entirely; other
|
|
22
|
+
# levels gate both sig contributions (`:true`/`:strict`/
|
|
23
|
+
# `:strong` only) and per-call assertion recognition
|
|
24
|
+
# (`T.let` / `T.cast` / `T.must` / etc.) via
|
|
25
|
+
# `Sorbet#assertion_enforced_here?`.
|
|
29
26
|
module SigilDetector
|
|
30
27
|
# Sorbet's strictness-level names. Stored as symbols to
|
|
31
28
|
# match the analyzer's existing convention for level
|
|
@@ -9,14 +9,6 @@ module Rigor
|
|
|
9
9
|
# block's `params(...)` and `returns(...)` clauses) into
|
|
10
10
|
# Rigor's internal type carriers.
|
|
11
11
|
#
|
|
12
|
-
# Slice 1 covered the minimum vocabulary that lets a
|
|
13
|
-
# typical `sig { params(x: Integer).returns(String) }`
|
|
14
|
-
# round-trip; slice 3 widens it to cover the dense middle
|
|
15
|
-
# of Sorbet's surface — generic class applications
|
|
16
|
-
# (`T::Array[E]`, `T::Hash[K, V]`, etc.), class-object
|
|
17
|
-
# types (`T.class_of(C)`, `T::Class[T]`), tuples, and
|
|
18
|
-
# shapes:
|
|
19
|
-
#
|
|
20
12
|
# | Sorbet form | Rigor carrier |
|
|
21
13
|
# | ------------------------ | ---------------------------------------- |
|
|
22
14
|
# | `Integer` etc. | `Nominal["Integer"]` |
|
|
@@ -42,10 +34,9 @@ module Rigor
|
|
|
42
34
|
#
|
|
43
35
|
# Anything else (`T.proc`, `T.attached_class`,
|
|
44
36
|
# `T.self_type`, `T.type_parameter`, `T::Struct` / `T::Enum`
|
|
45
|
-
# subclasses, …) degrades to `Dynamic[top]`. The
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# diagnostic.
|
|
37
|
+
# subclasses, …) degrades silently to `Dynamic[top]`. The
|
|
38
|
+
# `dynamic.sorbet.unsupported` diagnostic for degraded
|
|
39
|
+
# forms is deferred.
|
|
49
40
|
module TypeTranslator
|
|
50
41
|
BOOLEAN_NAME = "Boolean"
|
|
51
42
|
|
|
@@ -235,9 +226,8 @@ module Rigor
|
|
|
235
226
|
# analogue (Singleton names a specific class); the
|
|
236
227
|
# closest faithful translation is `Singleton[name]`
|
|
237
228
|
# when `T` is a constant, or `Singleton[Object]` for
|
|
238
|
-
# broader applications. Lossy
|
|
239
|
-
#
|
|
240
|
-
# surface lands.
|
|
229
|
+
# broader applications. Lossy — the `dynamic.sorbet.degraded`
|
|
230
|
+
# diagnostic for this case is deferred.
|
|
241
231
|
def translate_t_class_subscript(args)
|
|
242
232
|
inner = args.first
|
|
243
233
|
return Rigor::Type::Combinator.singleton_of("Class") if inner.nil?
|
|
@@ -29,11 +29,10 @@ module Rigor
|
|
|
29
29
|
# nesting; `def self.foo` is recognised as a singleton
|
|
30
30
|
# method.
|
|
31
31
|
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# `Dynamic[top]` for now — slice 3 widens the translator.
|
|
32
|
+
# The {TypeTranslator} table documents coverage. Most of
|
|
33
|
+
# Sorbet's vocabulary translates; remaining gaps (`T.proc`,
|
|
34
|
+
# `T::Struct` subclasses, `T.attached_class`, etc.) degrade
|
|
35
|
+
# silently to `Dynamic[top]`.
|
|
37
36
|
#
|
|
38
37
|
# Architecture: per-run `Catalog` is built lazily on first
|
|
39
38
|
# access by walking every configured `paths:` entry's `.rb`
|
|
@@ -99,20 +98,15 @@ module Rigor
|
|
|
99
98
|
# `false` to record every file's sigs regardless of
|
|
100
99
|
# sigil (current behaviour pre-this-config).
|
|
101
100
|
@enforce_sigil = config.fetch("enforce_sigil")
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
# `
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
# sigil hasn't been observed yet (e.g. the catalog
|
|
112
|
-
# hasn't run, or the call site is in a fixture /
|
|
113
|
-
# synthetic path the harvest didn't see) treat
|
|
114
|
-
# missing-info as enforced — failing-open is friendlier
|
|
115
|
-
# for spec ergonomics than failing-closed.
|
|
101
|
+
# Per-call-site assertion gating: `@sigil_by_path` (built
|
|
102
|
+
# during catalog harvest) is consulted so `T.let` /
|
|
103
|
+
# `T.cast` / `T.must` / `T.bind` / `T.assert_type!` only
|
|
104
|
+
# fire in files Sorbet itself would enforce (`:true` /
|
|
105
|
+
# `:strict` / `:strong`). With `enforce_sigil: false` the
|
|
106
|
+
# gate is open everywhere. Missing-sigil paths (synthetic
|
|
107
|
+
# fixtures, out-of-tree call sites) default to enforced —
|
|
108
|
+
# failing-open suits spec ergonomics better than
|
|
109
|
+
# failing-closed. See `assertion_enforced_here?`.
|
|
116
110
|
@sigil_by_path = {}
|
|
117
111
|
@catalog = nil
|
|
118
112
|
@parse_errors_by_path = {}
|
|
@@ -160,8 +154,7 @@ module Rigor
|
|
|
160
154
|
diagnostics
|
|
161
155
|
end
|
|
162
156
|
|
|
163
|
-
# ADR-52 slice 4 —
|
|
164
|
-
# the legacy `flow_contribution_for` hook onto the
|
|
157
|
+
# ADR-52 slice 4 — per-call return-type path via the
|
|
165
158
|
# method-name-gated `dynamic_return` DSL. The recognised name
|
|
166
159
|
# set is only known at run time (the catalog's `def` names come
|
|
167
160
|
# from the lazy catalog build), so it is declared as a
|
|
@@ -170,17 +163,15 @@ module Rigor
|
|
|
170
163
|
# resolved Symbol Set. The gate is a safe over-approximation —
|
|
171
164
|
# a project method merely *named* `cast` or `find` passes it
|
|
172
165
|
# and is declined by the block's own `T.`-receiver / catalog
|
|
173
|
-
# checks
|
|
166
|
+
# checks.
|
|
174
167
|
dynamic_return methods: -> { recognised_method_names } do |call_node, scope|
|
|
175
168
|
contribution_return_type(call_node, scope)
|
|
176
169
|
end
|
|
177
170
|
|
|
178
171
|
# ADR-52 slice 4 — `T.bind(self, T)`'s self-narrowing fact,
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
# `
|
|
182
|
-
# consults only this path for narrowing facts). The
|
|
183
|
-
# return-type half (`Constant[nil]`) flows through the
|
|
172
|
+
# contributed via the method-gated `type_specifier` DSL. The
|
|
173
|
+
# statement evaluator consults this path for narrowing facts.
|
|
174
|
+
# The return-type half (`Constant[nil]`) flows through the
|
|
184
175
|
# `dynamic_return` rule above; the block re-checks the `T.`
|
|
185
176
|
# receiver via the recogniser, so an unrelated `bind` call
|
|
186
177
|
# contributes nothing.
|
|
@@ -190,8 +181,8 @@ module Rigor
|
|
|
190
181
|
|
|
191
182
|
private
|
|
192
183
|
|
|
193
|
-
#
|
|
194
|
-
#
|
|
184
|
+
# Run-time method-name gate for the `dynamic_return` rule
|
|
185
|
+
# (ADR-52 slice 4): the static assertion vocabulary
|
|
195
186
|
# (`T.let` / `T.cast` / …), `T.absurd`, and every method name
|
|
196
187
|
# the catalog carries a sig for.
|
|
197
188
|
def recognised_method_names
|
|
@@ -202,20 +193,17 @@ module Rigor
|
|
|
202
193
|
names
|
|
203
194
|
end
|
|
204
195
|
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# the
|
|
208
|
-
# in two passes:
|
|
196
|
+
# Main contribution body for the `dynamic_return` rule.
|
|
197
|
+
# Returns the bare `Rigor::Type` the contract expects.
|
|
198
|
+
# Resolves the receiver in three passes:
|
|
209
199
|
#
|
|
210
200
|
# 1. Constant receiver (`User.find(...)`) → singleton-side
|
|
211
201
|
# catalog lookup.
|
|
212
202
|
# 2. Nominal receiver-type (`user.name` where `user`'s
|
|
213
203
|
# inferred type is `Nominal["User"]`) → instance-side
|
|
214
204
|
# catalog lookup.
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
# are deferred to slice 2 — slice 1 covers the common case
|
|
218
|
-
# where the sig is on the called method's own class.
|
|
205
|
+
# 3. Implicit-self (receiver-less inside a method body) →
|
|
206
|
+
# current-class lookup via `implicit_self_lookup`.
|
|
219
207
|
def contribution_return_type(call_node, scope)
|
|
220
208
|
return nil unless call_node.is_a?(Prism::CallNode)
|
|
221
209
|
|
|
@@ -286,9 +274,8 @@ module Rigor
|
|
|
286
274
|
contribution&.post_return_facts
|
|
287
275
|
end
|
|
288
276
|
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
# open (matches the pre-feature behaviour). With
|
|
277
|
+
# Per-call-site assertion gating (ADR-11). With
|
|
278
|
+
# `enforce_sigil: false` the gate is fully open. With
|
|
292
279
|
# `enforce_sigil: true` (default), the caller file's
|
|
293
280
|
# sigil must reach `:true` / `:strict` / `:strong` for
|
|
294
281
|
# assertions to fire. Three honest fallbacks:
|
|
@@ -329,9 +316,34 @@ module Rigor
|
|
|
329
316
|
chain_lookup(singleton_target, method_name, anchor_kind: :singleton, mixin_kind: :extend)
|
|
330
317
|
elsif receiver
|
|
331
318
|
instance_chain_lookup(receiver, method_name, scope)
|
|
319
|
+
else
|
|
320
|
+
implicit_self_lookup(method_name, scope)
|
|
332
321
|
end
|
|
333
322
|
end
|
|
334
323
|
|
|
324
|
+
# ADR-11 slice 2 — implicit-self calls.
|
|
325
|
+
# A receiver-less call inside a method body resolves against the
|
|
326
|
+
# engine's own `scope.self_type`: `Nominal[Foo]` inside an
|
|
327
|
+
# instance method (instance-side lookup), `Singleton[Foo]` inside
|
|
328
|
+
# a `def self.x` body (singleton-side lookup, `extend` mixins).
|
|
329
|
+
# Without this, an enforced sig on a sibling method was invisible
|
|
330
|
+
# to in-class calls — the engine's body-inference tiers then
|
|
331
|
+
# re-typed the sibling's body, overriding an explicit
|
|
332
|
+
# `T.untyped` opt-out (the dispatcher's plugin tier had already
|
|
333
|
+
# run and declined). Anything else (toplevel / Dynamic / DSL
|
|
334
|
+
# self) contributes nothing and the dispatcher continues.
|
|
335
|
+
def implicit_self_lookup(method_name, scope)
|
|
336
|
+
self_type = scope&.self_type
|
|
337
|
+
case self_type
|
|
338
|
+
when Rigor::Type::Singleton
|
|
339
|
+
chain_lookup(self_type.class_name, method_name, anchor_kind: :singleton, mixin_kind: :extend)
|
|
340
|
+
when Rigor::Type::Nominal
|
|
341
|
+
chain_lookup(self_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
|
|
342
|
+
end
|
|
343
|
+
rescue StandardError
|
|
344
|
+
nil
|
|
345
|
+
end
|
|
346
|
+
|
|
335
347
|
def instance_chain_lookup(receiver_node, method_name, scope)
|
|
336
348
|
return nil if scope.nil?
|
|
337
349
|
|
|
@@ -2,6 +2,9 @@ module Rigor
|
|
|
2
2
|
module Analysis
|
|
3
3
|
module CheckRules
|
|
4
4
|
def self?.diagnose: (path: String, root: untyped, scope_index: Hash[untyped, Scope]) -> Array[Diagnostic]
|
|
5
|
+
def self?.build_node_collectors: (String path, untyped scope_index) -> Hash[Symbol, untyped]
|
|
6
|
+
def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) -> untyped
|
|
7
|
+
def self?.shadow_verify_converged_collectors: (String path, untyped root, untyped scope_index, Hash[Symbol, untyped]? collectors) -> void
|
|
5
8
|
end
|
|
6
9
|
|
|
7
10
|
class FactStore
|
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, ?serialize: untyped, ?deserialize: untyped) { (untyped params) -> untyped } -> Symbol
|
|
17
|
+
def self.producer: (untyped id, ?watch: untyped, ?serialize: untyped, ?deserialize: 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
|
|
@@ -50,11 +50,14 @@ class Rigor::Plugin::Base
|
|
|
50
50
|
|
|
51
51
|
# Authoring helpers.
|
|
52
52
|
def diagnostic: (untyped node, path: untyped, message: untyped, ?severity: untyped, ?rule: untyped, ?location: untyped) -> untyped
|
|
53
|
+
def diagnostics_for: (untyped violations, path: untyped, ?node: untyped) -> Array[untyped]
|
|
54
|
+
def read_fact: (plugin_id: untyped, name: untyped) -> untyped
|
|
55
|
+
def producer_value: (untyped id, ?params: untyped) -> untyped
|
|
56
|
+
def producer_error: (untyped id) -> untyped
|
|
53
57
|
def manifest: () -> Rigor::Plugin::Manifest
|
|
54
58
|
def signature_paths: () -> Array[String]
|
|
55
59
|
def protocol_contracts: () -> untyped
|
|
56
60
|
def io_boundary: () -> Rigor::Plugin::IoBoundary
|
|
57
61
|
def cache_for: (untyped producer_id, ?params: untyped, ?descriptor: untyped) -> untyped
|
|
58
|
-
def glob_descriptor: (untyped roots, *untyped patterns) -> untyped
|
|
59
62
|
def plugin_entry: () -> untyped
|
|
60
63
|
end
|
|
@@ -3,7 +3,7 @@ class Rigor::Plugin::Manifest::Consumption
|
|
|
3
3
|
end
|
|
4
4
|
|
|
5
5
|
class Rigor::Plugin::Manifest
|
|
6
|
-
def initialize: (id: untyped, version: untyped, ?description: untyped, ?config_schema: untyped, ?produces: untyped, ?consumes: untyped, ?owns_receivers: untyped, ?open_receivers: untyped, ?type_node_resolvers: untyped, ?block_as_methods: untyped, ?heredoc_templates: untyped, ?nested_class_templates: untyped, ?trait_registries: untyped, ?
|
|
6
|
+
def initialize: (id: untyped, version: untyped, ?description: untyped, ?config_schema: untyped, ?produces: untyped, ?consumes: untyped, ?owns_receivers: untyped, ?open_receivers: untyped, ?type_node_resolvers: untyped, ?block_as_methods: untyped, ?heredoc_templates: untyped, ?nested_class_templates: untyped, ?trait_registries: untyped, ?hkt_registrations: untyped, ?hkt_definitions: untyped, ?signature_paths: untyped, ?protocol_contracts: untyped, ?source_rbs_synthesizer: untyped, ?additional_initializers: untyped) -> void
|
|
7
7
|
|
|
8
8
|
# Public attribute readers (the full `attr_reader` surface). Plugins
|
|
9
9
|
# read `manifest.id` / `manifest.protocol_contracts` etc.; declaring
|
|
@@ -25,7 +25,6 @@ class Rigor::Plugin::Manifest
|
|
|
25
25
|
def heredoc_templates: () -> untyped
|
|
26
26
|
def nested_class_templates: () -> untyped
|
|
27
27
|
def trait_registries: () -> untyped
|
|
28
|
-
def external_files: () -> untyped
|
|
29
28
|
def hkt_registrations: () -> untyped
|
|
30
29
|
def hkt_definitions: () -> untyped
|
|
31
30
|
def signature_paths: () -> untyped
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -11,6 +11,7 @@ module Rigor
|
|
|
11
11
|
attr_reader indexed_narrowings: Hash[IndexedKey, Type::t]
|
|
12
12
|
attr_reader method_chain_narrowings: Hash[ChainKey, Type::t]
|
|
13
13
|
attr_reader source_path: String?
|
|
14
|
+
attr_reader struct_fold_safe_locals: Set[Symbol]
|
|
14
15
|
|
|
15
16
|
# ADR-53 Track A — the seed-time discovery tables live on the
|
|
16
17
|
# DiscoveryIndex; Scope keeps per-table readers as delegates.
|
|
@@ -22,12 +23,15 @@ module Rigor
|
|
|
22
23
|
def in_source_constants: () -> Hash[String, Type::t]
|
|
23
24
|
def discovered_methods: () -> Hash[String, Hash[Symbol, Symbol]]
|
|
24
25
|
def discovered_def_nodes: () -> Hash[String, Hash[Symbol, untyped]]
|
|
26
|
+
def discovered_singleton_def_nodes: () -> Hash[String, Hash[Symbol, untyped]]
|
|
25
27
|
def discovered_def_sources: () -> Hash[String, Hash[Symbol, String]]
|
|
26
28
|
def discovered_method_visibilities: () -> Hash[String, Hash[Symbol, Symbol]]
|
|
27
29
|
def discovered_superclasses: () -> Hash[String, String]
|
|
28
30
|
def discovered_includes: () -> Hash[String, Array[String]]
|
|
29
31
|
def discovered_class_sources: () -> Hash[String, Set[String]]
|
|
30
32
|
def data_member_layouts: () -> Hash[String, Array[Symbol]]
|
|
33
|
+
def struct_member_layouts: () -> Hash[String, { members: Array[Symbol], keyword_init: bool }]
|
|
34
|
+
def param_inferred_types: () -> Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
31
35
|
|
|
32
36
|
class DiscoveryIndex
|
|
33
37
|
attr_reader declared_types: Hash[untyped, Type::t]
|
|
@@ -38,16 +42,19 @@ module Rigor
|
|
|
38
42
|
attr_reader in_source_constants: Hash[String, Type::t]
|
|
39
43
|
attr_reader discovered_methods: Hash[String, Hash[Symbol, Symbol]]
|
|
40
44
|
attr_reader discovered_def_nodes: Hash[String, Hash[Symbol, untyped]]
|
|
45
|
+
attr_reader discovered_singleton_def_nodes: Hash[String, Hash[Symbol, untyped]]
|
|
41
46
|
attr_reader discovered_def_sources: Hash[String, Hash[Symbol, String]]
|
|
42
47
|
attr_reader discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]]
|
|
43
48
|
attr_reader discovered_superclasses: Hash[String, String]
|
|
44
49
|
attr_reader discovered_includes: Hash[String, Array[String]]
|
|
45
50
|
attr_reader discovered_class_sources: Hash[String, Set[String]]
|
|
46
51
|
attr_reader data_member_layouts: Hash[String, Array[Symbol]]
|
|
52
|
+
attr_reader struct_member_layouts: Hash[String, { members: Array[Symbol], keyword_init: bool }]
|
|
53
|
+
attr_reader param_inferred_types: Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
47
54
|
|
|
48
55
|
EMPTY: DiscoveryIndex
|
|
49
56
|
|
|
50
|
-
def with: (?declared_types: Hash[untyped, Type::t], ?class_ivars: Hash[String, Hash[Symbol, Type::t]], ?class_cvars: Hash[String, Hash[Symbol, Type::t]], ?program_globals: Hash[Symbol, Type::t], ?discovered_classes: Hash[String, Type::Singleton], ?in_source_constants: Hash[String, Type::t], ?discovered_methods: Hash[String, Hash[Symbol, Symbol]], ?discovered_def_nodes: Hash[String, Hash[Symbol, untyped]], ?discovered_def_sources: Hash[String, Hash[Symbol, String]], ?discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]], ?discovered_superclasses: Hash[String, String], ?discovered_includes: Hash[String, Array[String]], ?discovered_class_sources: Hash[String, Set[String]], ?data_member_layouts: Hash[String, Array[Symbol]]) -> DiscoveryIndex
|
|
57
|
+
def with: (?declared_types: Hash[untyped, Type::t], ?class_ivars: Hash[String, Hash[Symbol, Type::t]], ?class_cvars: Hash[String, Hash[Symbol, Type::t]], ?program_globals: Hash[Symbol, Type::t], ?discovered_classes: Hash[String, Type::Singleton], ?in_source_constants: Hash[String, Type::t], ?discovered_methods: Hash[String, Hash[Symbol, Symbol]], ?discovered_def_nodes: Hash[String, Hash[Symbol, untyped]], ?discovered_singleton_def_nodes: Hash[String, Hash[Symbol, untyped]], ?discovered_def_sources: Hash[String, Hash[Symbol, String]], ?discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]], ?discovered_superclasses: Hash[String, String], ?discovered_includes: Hash[String, Array[String]], ?discovered_class_sources: Hash[String, Set[String]], ?data_member_layouts: Hash[String, Array[Symbol]], ?struct_member_layouts: Hash[String, { members: Array[Symbol], keyword_init: bool }], ?param_inferred_types: Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]) -> DiscoveryIndex
|
|
51
58
|
end
|
|
52
59
|
|
|
53
60
|
class IndexedKey
|
|
@@ -66,6 +73,8 @@ module Rigor
|
|
|
66
73
|
|
|
67
74
|
def initialize: (environment: Environment, locals: Hash[Symbol, Type::t], ?fact_store: Analysis::FactStore, ?self_type: Type::t?, ?ivars: Hash[Symbol, Type::t], ?cvars: Hash[Symbol, Type::t], ?globals: Hash[Symbol, Type::t], ?discovery: DiscoveryIndex, ?source_path: String?) -> void
|
|
68
75
|
def with_source_path: (String? path) -> Scope
|
|
76
|
+
def with_struct_fold_safe: (Set[Symbol] locals) -> Scope
|
|
77
|
+
def struct_fold_safe?: (String | Symbol name) -> bool
|
|
69
78
|
def with_discovery: (DiscoveryIndex index) -> Scope
|
|
70
79
|
def local: (String | Symbol name) -> Type::t?
|
|
71
80
|
def ivar: (String | Symbol name) -> Type::t?
|
|
@@ -75,16 +84,24 @@ module Rigor
|
|
|
75
84
|
def with_ivar: (String | Symbol name, Type::t type) -> Scope
|
|
76
85
|
def with_cvar: (String | Symbol name, Type::t type) -> Scope
|
|
77
86
|
def with_global: (String | Symbol name, Type::t type) -> Scope
|
|
87
|
+
def declaration_sourced: () -> Set[[Symbol, Symbol]]
|
|
88
|
+
def seed_declaration_sourced_ivar: (String | Symbol name, Type::t type) -> Scope
|
|
89
|
+
def with_declaration_sourced_local: (String | Symbol name, Type::t type) -> Scope
|
|
90
|
+
def with_local_declaration_mark: (String | Symbol name) -> Scope
|
|
91
|
+
def declaration_sourced?: (Symbol kind, String | Symbol name) -> bool
|
|
92
|
+
def forget_match_globals: () -> Scope
|
|
78
93
|
def class_ivars_for: (String | Symbol? class_name) -> Hash[Symbol, Type::t]
|
|
79
94
|
def class_cvars_for: (String | Symbol? class_name) -> Hash[Symbol, Type::t]
|
|
80
95
|
def discovered_method?: (String | Symbol class_name, String | Symbol method_name, Symbol kind) -> bool
|
|
81
96
|
def user_def_for: (String | Symbol class_name, String | Symbol method_name) -> untyped?
|
|
97
|
+
def singleton_def_for: (String | Symbol class_name, String | Symbol method_name) -> untyped?
|
|
82
98
|
def user_def_site_for: (String | Symbol class_name, String | Symbol method_name) -> String?
|
|
83
99
|
def top_level_def_for: (String | Symbol method_name) -> untyped?
|
|
84
100
|
def toplevel?: () -> bool
|
|
85
101
|
def discovered_method_visibility: (String | Symbol class_name, String | Symbol method_name) -> Symbol?
|
|
86
102
|
def superclass_of: (String | Symbol class_name) -> String?
|
|
87
103
|
def data_member_layout: (String | Symbol class_name) -> Array[Symbol]?
|
|
104
|
+
def struct_member_layout: (String | Symbol class_name) -> { members: Array[Symbol], keyword_init: bool }?
|
|
88
105
|
def includes_of: (String | Symbol class_name) -> Array[String]
|
|
89
106
|
def indexed_narrowing: (Symbol receiver_kind, String | Symbol receiver_name, untyped key) -> Type::t?
|
|
90
107
|
def with_indexed_narrowing: (Symbol receiver_kind, String | Symbol receiver_name, untyped key, Type::t type) -> Scope
|
data/sig/rigor/type.rbs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Rigor
|
|
2
2
|
module Type
|
|
3
|
-
type t = Top | Bot | Dynamic | Constant | IntegerRange | Nominal | Singleton | Union | Difference | Tuple | HashShape | DataClass | DataInstance
|
|
3
|
+
type t = Top | Bot | Dynamic | Constant | IntegerRange | Nominal | Singleton | Union | Difference | Tuple | HashShape | DataClass | DataInstance | StructClass | StructInstance
|
|
4
4
|
|
|
5
5
|
type accepts_mode = :strict | :gradual | :loose
|
|
6
6
|
|
|
@@ -277,6 +277,39 @@ module Rigor
|
|
|
277
277
|
def inspect: () -> String
|
|
278
278
|
end
|
|
279
279
|
|
|
280
|
+
class StructClass
|
|
281
|
+
attr_reader members: Array[Symbol]
|
|
282
|
+
attr_reader class_name: String?
|
|
283
|
+
attr_reader keyword_init: bool
|
|
284
|
+
def initialize: (Array[Symbol] members, ?String? class_name, ?keyword_init: bool) -> void
|
|
285
|
+
def describe: (?Symbol verbosity) -> String
|
|
286
|
+
def erase_to_rbs: () -> String
|
|
287
|
+
def top: () -> Trinary
|
|
288
|
+
def bot: () -> Trinary
|
|
289
|
+
def dynamic: () -> Trinary
|
|
290
|
+
def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
|
|
291
|
+
def ==: (untyped other) -> bool
|
|
292
|
+
def hash: () -> Integer
|
|
293
|
+
def inspect: () -> String
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
class StructInstance
|
|
297
|
+
attr_reader members: Hash[Symbol, Type::t]
|
|
298
|
+
attr_reader class_name: String?
|
|
299
|
+
def initialize: (Hash[Symbol, Type::t] members, ?String? class_name) -> void
|
|
300
|
+
def member_names: () -> Array[Symbol]
|
|
301
|
+
def member_type: (Symbol name) -> Type::t?
|
|
302
|
+
def describe: (?Symbol verbosity) -> String
|
|
303
|
+
def erase_to_rbs: () -> String
|
|
304
|
+
def top: () -> Trinary
|
|
305
|
+
def bot: () -> Trinary
|
|
306
|
+
def dynamic: () -> Trinary
|
|
307
|
+
def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
|
|
308
|
+
def ==: (untyped other) -> bool
|
|
309
|
+
def hash: () -> Integer
|
|
310
|
+
def inspect: () -> String
|
|
311
|
+
end
|
|
312
|
+
|
|
280
313
|
class AcceptsResult
|
|
281
314
|
attr_reader trinary: Trinary
|
|
282
315
|
attr_reader mode: accepts_mode
|
|
@@ -339,9 +372,12 @@ module Rigor
|
|
|
339
372
|
def self?.hash_shape_of: (?Hash[untyped, Type::t]? pairs, **untyped options) -> HashShape
|
|
340
373
|
def self?.data_class_of: (members: Array[Symbol], ?class_name: String?) -> DataClass
|
|
341
374
|
def self?.data_instance_of: (members: Hash[Symbol, Type::t], ?class_name: String?) -> DataInstance
|
|
375
|
+
def self?.struct_class_of: (members: Array[Symbol], ?class_name: String?, ?keyword_init: bool) -> StructClass
|
|
376
|
+
def self?.struct_instance_of: (members: Hash[Symbol, Type::t], ?class_name: String?) -> StructInstance
|
|
342
377
|
def self?.union: (*Type::t types) -> Type::t
|
|
343
378
|
def self?.key_of: (Type::t type) -> Type::t
|
|
344
379
|
def self?.value_of: (Type::t type) -> Type::t
|
|
380
|
+
def self?.widen_value_pinned: (Type::t type) -> Type::t
|
|
345
381
|
def self?.int_mask: (Array[Integer] flags) -> Type::t?
|
|
346
382
|
def self?.int_mask_of: (Type::t type) -> Type::t?
|
|
347
383
|
def self?.indexed_access: (Type::t type, Type::t key) -> Type::t
|
data/sig/rigor.rbs
CHANGED
|
@@ -11,7 +11,7 @@ module Rigor
|
|
|
11
11
|
attr_reader cache_path: String
|
|
12
12
|
attr_reader baseline_path: String?
|
|
13
13
|
|
|
14
|
-
def self.load: (?String path) -> Configuration
|
|
14
|
+
def self.load: (?String? path) -> Configuration
|
|
15
15
|
def self.discover: () -> String?
|
|
16
16
|
def self.load_with_includes: (String path, ?visited: Set[String]) -> Hash[String, untyped]
|
|
17
17
|
def initialize: (?Hash[String, untyped] data) -> void
|
|
@@ -28,6 +28,33 @@ signal — each hint has an `id`:
|
|
|
28
28
|
| `project-monkey-patch` | A DSL / monkey-patch Rigor can't see. | Escalate — a `pre_eval:` entry or a plugin clears the whole cluster. |
|
|
29
29
|
| `activerecord-relation-misinference` | Likely an engine gap. | Treat sites as candidate false positives (Phase 2). |
|
|
30
30
|
|
|
31
|
+
### `rigor triage --format json` `.selectors` — the by-(class, method) axis
|
|
32
|
+
|
|
33
|
+
Beside `hints`, the triage JSON carries a `selectors` array: one row
|
|
34
|
+
per dispatch target the diagnostics cluster on, built from the
|
|
35
|
+
structured `receiver_type` / `method_name` fields (never message
|
|
36
|
+
parsing). Each row is `{receiver, method, count, files, rules}`. Use
|
|
37
|
+
it to pick *which sites within a rule* to sample first — and to tell a
|
|
38
|
+
systemic cause from a scatter of real bugs — with `jq`, not eyeballing
|
|
39
|
+
the stream:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
# the dispatch targets responsible for the most diagnostics
|
|
43
|
+
rigor triage --format json | jq -r '.selectors[:15][] | "\(.count)\t\(.files)f\t\(.receiver)#\(.method)"'
|
|
44
|
+
# one method, one receiver, spread across many files → systemic
|
|
45
|
+
# (a plugin / pre_eval clears it) rather than N independent bugs
|
|
46
|
+
rigor triage --format json | jq '.selectors[] | select(.files >= 4)'
|
|
47
|
+
# narrow to a rule you are about to work, ranked by concentration
|
|
48
|
+
rigor triage --format json \
|
|
49
|
+
| jq '[.selectors[] | select(.rules["call.possible-nil-receiver"])] | sort_by(-.count)'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Read it as: **high `count` × high `files` = a systemic selector**
|
|
53
|
+
(escalate as a decision — one fix clears the cluster); **low `count` =
|
|
54
|
+
a candidate genuine bug** to sample directly in Phase 2. The `receiver`
|
|
55
|
+
is a normalised class (`"hi".squish` and `name.squish` both bucket
|
|
56
|
+
under `String#squish`), so a single idiom does not scatter across rows.
|
|
57
|
+
|
|
31
58
|
### `rigor baseline dump --format json` — the bucket list
|
|
32
59
|
|
|
33
60
|
```sh
|