rigortype 0.2.8 → 0.3.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 +2 -2
- data/data/core_overlay/csv.rbs +28 -0
- data/data/core_overlay/psych.rbs +22 -0
- data/docs/handbook/01-getting-started.md +9 -1
- data/docs/handbook/02-everyday-types.md +4 -1
- data/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +18 -9
- data/docs/handbook/08-understanding-errors.md +4 -3
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +99 -4
- data/docs/manual/02-cli-reference.md +65 -19
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +23 -0
- data/docs/manual/07-plugins.md +5 -5
- data/docs/manual/09-editor-integration.md +4 -4
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/docs/manual/14-rails-quickstart.md +4 -2
- data/docs/manual/15-type-protection-coverage.md +21 -0
- data/docs/manual/plugins/rigor-actionpack.md +1 -1
- data/docs/manual/plugins/rigor-activerecord.md +12 -5
- data/docs/manual/plugins/rigor-rails-routes.md +11 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +93 -0
- data/lib/rigor/analysis/check_rules.rb +557 -95
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +68 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +343 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/store.rb +207 -60
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +212 -27
- data/lib/rigor/cli/coverage_command.rb +56 -30
- data/lib/rigor/cli/diagnostic_formats.rb +4 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +163 -23
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/protection_fork_scan.rb +55 -0
- data/lib/rigor/cli/protection_report.rb +7 -1
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +1 -32
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +35 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +71 -43
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +379 -113
- data/lib/rigor/inference/fork_map.rb +87 -0
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +11 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +245 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +70 -10
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +460 -49
- data/lib/rigor/inference/statement_evaluator.rb +111 -17
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/plugin/base.rb +9 -22
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +24 -2
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +23 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +21 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +13 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +65 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +20 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +22 -8
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +191 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +19 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +48 -15
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +42 -12
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/environment.rbs +1 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +2 -3
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +49 -24
- data/lib/rigor/cli/ci_detector.rb +0 -89
|
@@ -8,9 +8,10 @@ require "rigor/plugin"
|
|
|
8
8
|
# name: T`, `# @rbs return: T`, attribute `#:`, …) and contributes the result to the analysis environment
|
|
9
9
|
# through the `source_rbs_synthesizer:` manifest hook.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# Since ADR-93 WD1 the default is `require_magic_comment: false`: a file is processed whenever it actually
|
|
12
|
+
# carries an annotation (see {Synthesizer#annotated?}), with only the upstream `# rbs_inline: disabled`
|
|
13
|
+
# directive opting a file out. Set `require_magic_comment: true` to restore the old ADR-32 WD2 gate that
|
|
14
|
+
# processed only files opening with `# rbs_inline: enabled`.
|
|
14
15
|
module Rigor
|
|
15
16
|
module Plugin
|
|
16
17
|
# The plugin gem requires `rbs/inline` at load time; without the upstream library the synthesizer can't
|
|
@@ -35,9 +36,24 @@ module Rigor
|
|
|
35
36
|
# env-build time. Returns the synthesised RBS source as a String, or `nil` when the file contributes
|
|
36
37
|
# nothing (no magic comment in the default mode, empty annotation set, parse error per WD6).
|
|
37
38
|
class Synthesizer
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
39
|
+
# An RDoc directive comment: `#:` followed by a bare word (RDoc allows a hyphen, as in `:call-seq:`)
|
|
40
|
+
# and a closing colon. RDoc predates rbs-inline by about two decades and `#:<name>:` is
|
|
41
|
+
# indistinguishable from an rbs-inline `#: <type>` assertion to upstream's parser, which reads the
|
|
42
|
+
# directive name as a type alias — `def f #:nodoc:` becomes `def f: () -> nodoc`, a type nothing
|
|
43
|
+
# declares (upstream issue: https://github.com/soutaro/rbs-inline/issues/248).
|
|
44
|
+
#
|
|
45
|
+
# Matching on shape rather than a name list covers all 17 directives the Ruby docs list
|
|
46
|
+
# (https://docs.ruby-lang.org/ja/latest/library/rdoc.html) with nothing to maintain, and none of
|
|
47
|
+
# `#: () -> void`, `#: String`, `#:Integer`, `#: bool`, `#:my_alias`, `#: { name: String }`,
|
|
48
|
+
# `#: (name: String) -> void`, `#: Integer?`, `#[Integer]` or `# @rbs x: String` matches it: a valid
|
|
49
|
+
# RBS type never starts with a bare lowercase word that is immediately closed by a colon. Only the
|
|
50
|
+
# space-free spelling is affected; `# :nodoc:` never reaches upstream's annotation grammar.
|
|
51
|
+
RDOC_DIRECTIVE_COMMENT = /\A#:[a-z_][\w-]*:/
|
|
52
|
+
|
|
53
|
+
# @param require_magic_comment [Boolean] when `false` (the default since ADR-93 WD1), the magic
|
|
54
|
+
# comment is not required and the file is processed only if it actually carries an annotation — see
|
|
55
|
+
# {#annotated?}. When `true` (the old ADR-32 WD2 gate), only files opening with
|
|
56
|
+
# `# rbs_inline: enabled` are processed, and upstream's opt-in semantics apply verbatim.
|
|
41
57
|
def initialize(require_magic_comment:)
|
|
42
58
|
@require_magic_comment = require_magic_comment
|
|
43
59
|
freeze
|
|
@@ -55,6 +71,9 @@ module Rigor
|
|
|
55
71
|
return nil if source.empty?
|
|
56
72
|
|
|
57
73
|
result = ::Prism.parse(source)
|
|
74
|
+
_, result = neutralize_rdoc_directives(source, result)
|
|
75
|
+
return nil if !@require_magic_comment && !annotated?(result)
|
|
76
|
+
|
|
58
77
|
# `opt_in: true` is rbs-inline's "require the magic comment" mode (per upstream parser.rb:62).
|
|
59
78
|
# The plugin's `require_magic_comment:` config knob maps directly onto it.
|
|
60
79
|
parsed = ::RBS::Inline::Parser.parse(result, opt_in: @require_magic_comment)
|
|
@@ -71,6 +90,56 @@ module Rigor
|
|
|
71
90
|
# message, without crashing analysis.
|
|
72
91
|
[:error, "#{e.class}: #{e.message.to_s.lines.first.to_s.strip}"]
|
|
73
92
|
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
# True when the file carries at least one rbs-inline annotation. Gates the magic-comment-free mode,
|
|
97
|
+
# and is the difference between "honour annotations wherever they are" and "fabricate signatures for
|
|
98
|
+
# code nobody annotated" — upstream's opt-out mode does the latter, emitting a full
|
|
99
|
+
# `def f: (untyped x) -> untyped` skeleton for EVERY unannotated def. Rigor trusts an accepted
|
|
100
|
+
# signature over body inference, so those skeletons would replace real inferred types with `untyped`:
|
|
101
|
+
# measured on mail (zero annotations) as 26 → 42 diagnostics, i.e. the mode actively fought the
|
|
102
|
+
# analysis on exactly the projects that write no annotations. The spec binds Rigor to treat
|
|
103
|
+
# annotations as type sources "whenever present" (`overview.md` § "Compatibility hierarchy"); it does
|
|
104
|
+
# not ask for untyped shadows of unannotated code.
|
|
105
|
+
#
|
|
106
|
+
# Detection delegates to upstream's own `AnnotationParser` rather than scanning for `#:` / `@rbs`
|
|
107
|
+
# with a regexp: the annotation grammar is upstream's to define (ADR-32 WD3), and this keeps a doc
|
|
108
|
+
# comment that merely mentions `@rbs`, or a URL containing `#:`, from opting a file in. A file with
|
|
109
|
+
# the magic comment keeps upstream's semantics verbatim — the author opted that file in explicitly,
|
|
110
|
+
# skeletons and all, which is what `rbs-inline --output` would generate for it.
|
|
111
|
+
def annotated?(prism_result)
|
|
112
|
+
::RBS::Inline::AnnotationParser.parse(prism_result.comments)
|
|
113
|
+
.any? { |parsed| parsed.each_annotation.any? }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Rewrite every RDoc directive comment to its spaced spelling (`#:nodoc:` -> `# :nodoc:`) so
|
|
117
|
+
# upstream's annotation grammar never sees it, and re-parse. Two reasons this happens here rather
|
|
118
|
+
# than in {#annotated?}: the directive must not gate a file in, AND it must not reach the synthesis,
|
|
119
|
+
# because one mis-parsed directive takes the whole class down with it. `def f #:nodoc:` renders as
|
|
120
|
+
# `def f: (untyped x) -> nodoc`; `nodoc` resolves to nothing, `RBS::DefinitionBuilder` raises
|
|
121
|
+
# `NoTypeFoundError` for the class, and every real annotation in that class is silently lost —
|
|
122
|
+
# measured on a class whose `#: (String) -> Integer` method fell back to body inference because a
|
|
123
|
+
# sibling method carried `#:nodoc:`. `fileutils.rb` alone carries 29 of these.
|
|
124
|
+
#
|
|
125
|
+
# The rewrite touches comment text only, so it cannot change what Ruby does; it shifts columns
|
|
126
|
+
# within the comment, which nothing downstream reads (the synthesized RBS is fresh text with its own
|
|
127
|
+
# buffer). Prism decides what is a comment, so a `#:nodoc:` inside a string literal stays put. Files
|
|
128
|
+
# without a directive re-parse nothing.
|
|
129
|
+
def neutralize_rdoc_directives(source, prism_result)
|
|
130
|
+
offsets = prism_result.comments.filter_map do |comment|
|
|
131
|
+
location = comment.location
|
|
132
|
+
# `start_character_offset`, not `start_offset`: the latter counts BYTES while `String#insert`
|
|
133
|
+
# indexes CHARACTERS, so on a file with any multi-byte content the space lands mid-word and
|
|
134
|
+
# rewrites `#:nodoc:` to `#:n odoc:` — still a directive to upstream, and now a corrupted one.
|
|
135
|
+
location.start_character_offset if RDOC_DIRECTIVE_COMMENT.match?(location.slice)
|
|
136
|
+
end
|
|
137
|
+
return [source, prism_result] if offsets.empty?
|
|
138
|
+
|
|
139
|
+
# Back to front, so each insertion leaves the earlier offsets valid.
|
|
140
|
+
rewritten = offsets.sort.reverse.inject(source.dup) { |acc, offset| acc.insert(offset + 1, " ") }
|
|
141
|
+
[rewritten, ::Prism.parse(rewritten)]
|
|
142
|
+
end
|
|
74
143
|
end
|
|
75
144
|
|
|
76
145
|
manifest(
|
|
@@ -87,11 +156,16 @@ module Rigor
|
|
|
87
156
|
# override (returned by `#manifest`, which `Plugin::Registry#source_rbs_synthesizers` consults via
|
|
88
157
|
# `plugin.manifest.source_rbs_synthesizer`).
|
|
89
158
|
#
|
|
90
|
-
# ADR-
|
|
91
|
-
#
|
|
159
|
+
# ADR-93 WD1 — `require_magic_comment` defaults to `false`: annotations are official type sources
|
|
160
|
+
# "always parsed whenever present" per the binding spec (overview.md § Compatibility hierarchy), so
|
|
161
|
+
# a file is processed when it actually carries an annotation, with the magic comment not required.
|
|
162
|
+
# The magic-comment-free mode is annotation-presence-gated (see {Synthesizer#annotated?}), so an
|
|
163
|
+
# unannotated file contributes nothing and the flip cannot regress it. Set `require_magic_comment:
|
|
164
|
+
# true` in `.rigor.yml` to restore the ADR-32 opt-in behaviour; `# rbs_inline: disabled` remains the
|
|
165
|
+
# per-file opt-out either way (honoured by upstream unconditionally).
|
|
92
166
|
def initialize(services:, config: {})
|
|
93
167
|
super
|
|
94
|
-
@require_magic_comment = config.fetch("require_magic_comment",
|
|
168
|
+
@require_magic_comment = config.fetch("require_magic_comment", false) ? true : false
|
|
95
169
|
@synthesizer = Synthesizer.new(require_magic_comment: @require_magic_comment)
|
|
96
170
|
# Build the per-instance manifest eagerly (before `freeze`) so the registry's repeated reads
|
|
97
171
|
# return the same object and we don't need to mutate a frozen instance later.
|
|
@@ -3,16 +3,20 @@
|
|
|
3
3
|
# rigor-rbs-inline — ingests rbs-inline-shaped comments (`# @rbs name: T`, `#: () -> T`, `# @rbs return: T`,
|
|
4
4
|
# attribute `#:`, ivars, generics, override, …) as RBS contributions to the analysis environment.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# Since ADR-93 WD1 the plugin defaults to `require_magic_comment: false`: annotations are official type
|
|
7
|
+
# sources wherever present, gated only on a file actually carrying one, with `# rbs_inline: disabled` the
|
|
8
|
+
# per-file opt-out. ADR-93 WD2 default-wires this plugin from `Configuration.load` when the upstream
|
|
9
|
+
# `rbs-inline` library is resolvable, so most projects need no `plugins:` entry at all. Restore the old
|
|
10
|
+
# ADR-32 magic-comment gate, or opt out of the auto-wire, through config:
|
|
10
11
|
#
|
|
11
12
|
# # .rigor.yml
|
|
12
13
|
# plugins:
|
|
13
14
|
# - id: rigor-rbs-inline
|
|
14
15
|
# config:
|
|
15
|
-
# require_magic_comment:
|
|
16
|
+
# require_magic_comment: true # default false — restore the ADR-32 magic-comment gate
|
|
17
|
+
# # or disable the auto-wired default entirely:
|
|
18
|
+
# # - gem: rigor-rbs-inline
|
|
19
|
+
# # enabled: false
|
|
16
20
|
#
|
|
17
21
|
# The plugin depends on the upstream `rbs-inline` gem; Rigor's core `rigortype` gemspec stays zero-dep per
|
|
18
22
|
# ADR-0 / ADR-32 WD1.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rigor/source/node_children"
|
|
4
|
+
|
|
3
5
|
require "prism"
|
|
4
6
|
require "rigor/source/literals"
|
|
5
7
|
|
|
@@ -89,7 +91,7 @@ module Rigor
|
|
|
89
91
|
return
|
|
90
92
|
end
|
|
91
93
|
|
|
92
|
-
node.
|
|
94
|
+
node.rigor_each_child do |child|
|
|
93
95
|
collect(child, anchor: anchor, accumulator: accumulator)
|
|
94
96
|
end
|
|
95
97
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rigor/source/node_children"
|
|
4
|
+
|
|
3
5
|
require "prism"
|
|
4
6
|
require "rigor/source/literals"
|
|
5
7
|
|
|
@@ -70,7 +72,7 @@ module Rigor
|
|
|
70
72
|
if rspec_describe_call?(node)
|
|
71
73
|
scopes << build_scope(node, kind: :rspec_describe)
|
|
72
74
|
else
|
|
73
|
-
node.
|
|
75
|
+
node.rigor_each_child { |child| walk_top_level(child, scopes) }
|
|
74
76
|
end
|
|
75
77
|
end
|
|
76
78
|
|
|
@@ -97,6 +97,17 @@ module Rigor
|
|
|
97
97
|
let_binding_return_type(call_node, scope)
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
# ADR-88 WD1 — both contributions are per-file: the matcher narrowing facts come from the call's own AST,
|
|
101
|
+
# and the `let`-binding return types come from each spec file's OWN `describe`/`let` structure (`let` is
|
|
102
|
+
# file-scoped). A spec file's edit changes its own content (re-analysed already). The one cross-file input
|
|
103
|
+
# — the optional factorybot `:factory_index` fact this plugin consumes — is itself an ADR-9 publication
|
|
104
|
+
# captured in the incremental fact-surface digest via factorybot's own channel, so it need not appear
|
|
105
|
+
# here. A stable sentinel declares "no OWN cross-file fact surface", keeping the plugin
|
|
106
|
+
# incremental-capable; `--verify-incremental` backstops the claim.
|
|
107
|
+
def incremental_state_fingerprint
|
|
108
|
+
"per-file-lets"
|
|
109
|
+
end
|
|
110
|
+
|
|
100
111
|
private
|
|
101
112
|
|
|
102
113
|
# The `file_methods:` gate set — every `let` / `subject` name the file declares anywhere. A safe
|
|
@@ -43,7 +43,10 @@ module Rigor
|
|
|
43
43
|
|
|
44
44
|
require "rack/utils"
|
|
45
45
|
@rack_status_symbols = Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.to_set.freeze
|
|
46
|
-
rescue LoadError, StandardError
|
|
46
|
+
rescue ::LoadError, ::StandardError
|
|
47
|
+
# `::`-qualified: in this lexical scope a bare `LoadError` resolves to `Rigor::Plugin::LoadError`
|
|
48
|
+
# (the plugin-loading error, a StandardError), so the real `::LoadError` (a ScriptError) from a
|
|
49
|
+
# failed require would escape instead of declining.
|
|
47
50
|
@rack_status_symbols = nil
|
|
48
51
|
end
|
|
49
52
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rigor/source/node_children"
|
|
4
|
+
|
|
3
5
|
require "prism"
|
|
4
6
|
|
|
5
7
|
require_relative "worker_index"
|
|
@@ -67,7 +69,7 @@ module Rigor
|
|
|
67
69
|
when Prism::ClassNode then visit_class(node, lexical_path, &)
|
|
68
70
|
when Prism::ModuleNode then visit_module(node, lexical_path, &)
|
|
69
71
|
else
|
|
70
|
-
node.
|
|
72
|
+
node.rigor_each_child { |child| walk_for_workers(child, lexical_path, &) }
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
|
|
@@ -133,7 +135,7 @@ module Rigor
|
|
|
133
135
|
def lookup_perform_def(body)
|
|
134
136
|
return nil if body.nil?
|
|
135
137
|
|
|
136
|
-
body.
|
|
138
|
+
body.rigor_each_child do |node|
|
|
137
139
|
next unless node.is_a?(Prism::DefNode) && node.name == :perform
|
|
138
140
|
next if node.receiver.is_a?(Prism::SelfNode)
|
|
139
141
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rigor/source/node_children"
|
|
4
|
+
|
|
3
5
|
require "prism"
|
|
4
6
|
|
|
5
7
|
require_relative "method_signature"
|
|
@@ -58,7 +60,7 @@ module Rigor
|
|
|
58
60
|
# A `def` not preceded by a `sig` is fine; we just don't record anything for it. The
|
|
59
61
|
# interesting case is in `walk_statements`, which pairs sig+def.
|
|
60
62
|
else
|
|
61
|
-
node.
|
|
63
|
+
node.rigor_each_child do |child|
|
|
62
64
|
walk_node(child, state, lexical_path: lexical_path, in_singleton_class: in_singleton_class)
|
|
63
65
|
end
|
|
64
66
|
end
|
|
@@ -116,6 +116,18 @@ module Rigor
|
|
|
116
116
|
errors.map { |error| parse_error_diagnostic(path, error) }
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
# ADR-88 WD2 — the per-run catalog is an ADR-60 record-and-validate producer. Before this it was rebuilt
|
|
120
|
+
# unconditionally on every `--incremental` recheck (parse + walk every `.rb` / `.rbi` sig tree); as a
|
|
121
|
+
# producer it is cached to disk keyed on the plugin config, with `watch:` covering the scanned trees so an
|
|
122
|
+
# added / removed / edited sig file recomputes it. The producer VALUE (a Marshal-clean bundle of the
|
|
123
|
+
# catalog + the sigil map + the parse-error tuples) is what ADR-88 WD1 digests into the incremental
|
|
124
|
+
# fact-surface fingerprint, so a sig edit OUTSIDE `signature_paths:` invalidates the snapshot for free.
|
|
125
|
+
# The bundle is returned (not written to ivars) because a cache HIT does not run the block — the sigil map
|
|
126
|
+
# and parse errors would otherwise be lost on the warm path.
|
|
127
|
+
producer :catalog, watch: -> { catalog_watch_globs } do |_params|
|
|
128
|
+
build_catalog_bundle
|
|
129
|
+
end
|
|
130
|
+
|
|
119
131
|
# ADR-52 slice 4 — per-call return-type path via the method-name-gated `dynamic_return` DSL. The
|
|
120
132
|
# recognised name set is only known at run time (the catalog's `def` names come from the lazy
|
|
121
133
|
# catalog build), so it is declared as a callable: the engine `instance_exec`s it once per run on
|
|
@@ -389,35 +401,69 @@ module Rigor
|
|
|
389
401
|
end
|
|
390
402
|
end
|
|
391
403
|
|
|
404
|
+
# ADR-88 WD2 — resolve the catalog from the `:catalog` producer (disk-cached) and unpack its Marshal-clean
|
|
405
|
+
# bundle into the ivars the return-type / sigil-gate / parse-error paths read. `producer_value` memoises
|
|
406
|
+
# per instance and rescues a producer failure to nil, hence the empty-bundle fallback.
|
|
392
407
|
def ensure_catalog
|
|
393
408
|
return @catalog if @catalog_built
|
|
394
409
|
|
|
410
|
+
bundle = producer_value(:catalog) || EMPTY_CATALOG_BUNDLE
|
|
411
|
+
@catalog = bundle[:catalog]
|
|
412
|
+
@sigil_by_path = bundle[:sigil_by_path]
|
|
413
|
+
@parse_errors_by_path = bundle[:parse_errors_by_path]
|
|
414
|
+
@catalog_built = true
|
|
415
|
+
@catalog
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# ADR-88 WD2 — the `watch:` coverage for the `:catalog` producer: the `.rb` sig trees + the `.rbi` trees.
|
|
419
|
+
# An added / removed file under either invalidates the cached bundle even though the block globbed the
|
|
420
|
+
# tree itself. Evaluated at `cache_for` time (after `#init`), so `@configured_paths` / `@rbi_paths` are set.
|
|
421
|
+
def catalog_watch_globs
|
|
422
|
+
[[@configured_paths, "**/*.rb"], [@rbi_paths, "**/*.rbi"]]
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# ADR-88 WD2 — build the Marshal-clean catalog bundle the `:catalog` producer caches. Harvests the `.rb`
|
|
426
|
+
# sig trees then the `.rbi` trees (last-wins ordering preserved from the ivar-mutating original) and
|
|
427
|
+
# returns `{ catalog:, sigil_by_path:, parse_errors_by_path: }`.
|
|
428
|
+
def build_catalog_bundle
|
|
395
429
|
catalog = Catalog.new
|
|
430
|
+
sigil_by_path = {}
|
|
431
|
+
parse_errors_by_path = {}
|
|
396
432
|
# Project source — `.rb` only.
|
|
397
|
-
@configured_paths.each { |root| harvest_path(root, catalog,
|
|
433
|
+
@configured_paths.each { |root| harvest_path(root, catalog, sigil_by_path, parse_errors_by_path, %w[.rb]) }
|
|
398
434
|
# Sorbet RBI tree — `.rbi` only. Slice 4 of ADR-11.
|
|
399
|
-
@rbi_paths.each { |root| harvest_path(root, catalog,
|
|
435
|
+
@rbi_paths.each { |root| harvest_path(root, catalog, sigil_by_path, parse_errors_by_path, %w[.rbi]) }
|
|
400
436
|
catalog.freeze!
|
|
401
|
-
|
|
402
|
-
@catalog_built = true
|
|
403
|
-
catalog
|
|
437
|
+
{ catalog: catalog, sigil_by_path: sigil_by_path, parse_errors_by_path: parse_errors_by_path }
|
|
404
438
|
end
|
|
405
439
|
|
|
440
|
+
# Frozen empty bundle used when the `:catalog` producer failed (e.g. a project I/O error) so downstream
|
|
441
|
+
# reads see a well-formed shape rather than nil.
|
|
442
|
+
EMPTY_CATALOG_BUNDLE = { catalog: Catalog.new.freeze!, sigil_by_path: {}, parse_errors_by_path: {} }.freeze
|
|
443
|
+
private_constant :EMPTY_CATALOG_BUNDLE
|
|
444
|
+
|
|
406
445
|
# @param root [String] directory or single file.
|
|
407
446
|
# @param catalog [Catalog]
|
|
447
|
+
# @param sigil_by_path [Hash{String=>Symbol}] accumulator: harvested file → detected sigil level.
|
|
448
|
+
# @param parse_errors_by_path [Hash{String=>Array<Hash>}] accumulator: file → `{kind:,line:,column:}` tuples.
|
|
408
449
|
# @param extensions [Array<String>] file extensions to accept (e.g. `[".rb"]` for project source,
|
|
409
450
|
# `[".rbi"]` for Sorbet RBI tree).
|
|
410
|
-
def harvest_path(root, catalog, extensions
|
|
451
|
+
def harvest_path(root, catalog, sigil_by_path, parse_errors_by_path, extensions)
|
|
411
452
|
absolute = canonicalize(root)
|
|
412
453
|
if File.directory?(absolute)
|
|
413
454
|
extensions.each do |ext|
|
|
455
|
+
# ADR-88 WD2 — deterministic fold order matters now that the catalog VALUE is digested into the
|
|
456
|
+
# incremental fact-surface fingerprint: a duplicate `(class, method, kind)` sig's last-wins winner
|
|
457
|
+
# must not vary by machine / run. `Dir.glob` sorts its results by default on Ruby 3.0+, so the fold
|
|
458
|
+
# order is already stable (the slice-1 comment's "filesystem order" caveat predates that default);
|
|
459
|
+
# this walk relies on it rather than re-sorting.
|
|
414
460
|
Dir.glob(File.join(absolute, "**", "*#{ext}")).each do |path|
|
|
415
|
-
harvest_file(canonicalize(path), catalog)
|
|
461
|
+
harvest_file(canonicalize(path), catalog, sigil_by_path, parse_errors_by_path)
|
|
416
462
|
end
|
|
417
463
|
end
|
|
418
464
|
elsif File.file?(absolute) && extensions.any? { |ext| absolute.end_with?(ext) }
|
|
419
465
|
# `paths:` may list individual files (the demos do this); walk them directly rather than skipping.
|
|
420
|
-
harvest_file(absolute, catalog)
|
|
466
|
+
harvest_file(absolute, catalog, sigil_by_path, parse_errors_by_path)
|
|
421
467
|
end
|
|
422
468
|
end
|
|
423
469
|
|
|
@@ -432,7 +478,7 @@ module Rigor
|
|
|
432
478
|
expanded
|
|
433
479
|
end
|
|
434
480
|
|
|
435
|
-
def harvest_file(path, catalog)
|
|
481
|
+
def harvest_file(path, catalog, sigil_by_path, parse_errors_by_path)
|
|
436
482
|
contents = io_boundary.read_file(path)
|
|
437
483
|
return if contents.nil?
|
|
438
484
|
|
|
@@ -441,7 +487,7 @@ module Rigor
|
|
|
441
487
|
# Per-call-site assertion gating consults this map at recognition time. Recorded BEFORE the
|
|
442
488
|
# ignored short-circuit so a `# typed: ignore` file still reports its level to the gate (the gate
|
|
443
489
|
# then chooses to suppress assertions there too — `ignore` is stricter than `false`).
|
|
444
|
-
|
|
490
|
+
sigil_by_path[path] = level
|
|
445
491
|
return if SigilDetector.ignored?(level)
|
|
446
492
|
|
|
447
493
|
result = Prism.parse(contents)
|
|
@@ -460,13 +506,23 @@ module Rigor
|
|
|
460
506
|
end
|
|
461
507
|
|
|
462
508
|
errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path)
|
|
463
|
-
|
|
509
|
+
# ADR-88 WD2 — store Marshal-clean `{kind:, line:, column:}` tuples, not the `ParseError` (it holds a
|
|
510
|
+
# live Prism node the producer bundle could not serialise). `diagnostics_for_file` rebuilds the
|
|
511
|
+
# diagnostic from the tuple, at the same 1-based line / `start_column + 1` position `from_node` gave.
|
|
512
|
+
parse_errors_by_path[path] = errors.map { |error| parse_error_tuple(error) } unless errors.empty?
|
|
464
513
|
rescue Plugin::AccessDeniedError, Errno::ENOENT
|
|
465
514
|
# Skip files outside the trusted read scope or that vanished between glob and read; the plugin
|
|
466
515
|
# produces no output for them.
|
|
467
516
|
nil
|
|
468
517
|
end
|
|
469
518
|
|
|
519
|
+
# ADR-88 WD2 — the Marshal-clean position tuple for a `CatalogWalker::ParseError`, capturing the node's
|
|
520
|
+
# location at harvest time so the producer bundle carries no live Prism node.
|
|
521
|
+
def parse_error_tuple(error)
|
|
522
|
+
location = error.node.location
|
|
523
|
+
{ kind: error.kind, line: location.start_line, column: location.start_column + 1 }
|
|
524
|
+
end
|
|
525
|
+
|
|
470
526
|
# Emits a `plugin.sorbet.absurd-reachable` warning for the `T.absurd(x)` call recorded in
|
|
471
527
|
# `@reachable_absurd_nodes` during inference; the node rule above does the identity match and pop.
|
|
472
528
|
def absurd_diagnostic(path, call_node)
|
|
@@ -535,11 +591,15 @@ module Rigor
|
|
|
535
591
|
)
|
|
536
592
|
end
|
|
537
593
|
|
|
594
|
+
# ADR-88 WD2 — `error` is a Marshal-clean `{kind:, line:, column:}` tuple (the pre-computed node
|
|
595
|
+
# location), not a Prism-node-bearing `ParseError`; build the diagnostic directly at that position (the
|
|
596
|
+
# `line` / `column` `from_node` would have derived).
|
|
538
597
|
def parse_error_diagnostic(path, error)
|
|
539
|
-
Rigor::Analysis::Diagnostic.
|
|
540
|
-
error.node,
|
|
598
|
+
Rigor::Analysis::Diagnostic.new(
|
|
541
599
|
path: path,
|
|
542
|
-
|
|
600
|
+
line: error[:line],
|
|
601
|
+
column: error[:column],
|
|
602
|
+
message: parse_error_message(error[:kind]),
|
|
543
603
|
severity: :warning,
|
|
544
604
|
rule: "parse-error"
|
|
545
605
|
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Declares the reopening `Rigor::Source::NodeChildren` performs at load: a compiled `#rigor_each_child` is defined on
|
|
2
|
+
# every concrete `Prism::*Node` class (allocation-free equivalent of `compact_child_nodes.each`). Declared on the
|
|
3
|
+
# abstract base so every subclass resolves; the runtime definition is per-concrete-class, generated from
|
|
4
|
+
# `Prism::Reflection` in lib/rigor/source/node_children.rb.
|
|
5
|
+
module Prism
|
|
6
|
+
class Node
|
|
7
|
+
def rigor_each_child: () { (Prism::Node) -> void } -> void
|
|
8
|
+
end
|
|
9
|
+
end
|
data/sig/rigor/environment.rbs
CHANGED
|
@@ -29,6 +29,7 @@ module Rigor
|
|
|
29
29
|
def rbs_module?: (String | Symbol name) -> bool
|
|
30
30
|
def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
|
|
31
31
|
def reflection: () -> untyped?
|
|
32
|
+
def missing_rbs_gem_owner: (String | Symbol root_constant_name) -> String?
|
|
32
33
|
|
|
33
34
|
class ClassRegistry
|
|
34
35
|
def self.default: () -> ClassRegistry
|
data/sig/rigor/inference.rbs
CHANGED
|
@@ -3,8 +3,10 @@ module Rigor
|
|
|
3
3
|
type closure_classification = :non_escaping | :escaping | :unknown
|
|
4
4
|
|
|
5
5
|
class ExpressionTyper
|
|
6
|
+
def self.harvest_return_memo: () -> Hash[untyped, Array[untyped]]
|
|
6
7
|
def initialize: (scope: Scope, ?tracer: FallbackTracer?) -> void
|
|
7
8
|
def type_of: (untyped node) -> Type::t
|
|
9
|
+
def return_type_for: (untyped def_node, Type::t receiver, Array[Type::t] arg_types) -> Type::t?
|
|
8
10
|
def scope: () -> Scope
|
|
9
11
|
def tracer: () -> FallbackTracer?
|
|
10
12
|
def dynamic_top: () -> Type::Dynamic
|
|
@@ -27,6 +29,7 @@ module Rigor
|
|
|
27
29
|
|
|
28
30
|
def initialize: (scope: Scope, ?tracer: FallbackTracer?, ?on_enter: (^(untyped, Scope) -> void)?, ?class_context: Array[ClassFrame]) -> void
|
|
29
31
|
def evaluate: (untyped node) -> [Type::t, Scope]
|
|
32
|
+
def content_mutated_parameter_positions: (untyped def_node) -> Array[Integer]
|
|
30
33
|
def scope: () -> Scope
|
|
31
34
|
def tracer: () -> FallbackTracer?
|
|
32
35
|
def sub_eval: (untyped node, Scope with_scope, ?class_context: Array[ClassFrame]) -> [Type::t, Scope]
|
|
@@ -163,6 +166,10 @@ module Rigor
|
|
|
163
166
|
def self?.record_declarations: (untyped node, Array[String] qualified_prefix, Hash[untyped, Type::t] identity_table, Hash[String, Type::t] discovered) -> void
|
|
164
167
|
def self?.discovered_classes_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[String, Type::t]
|
|
165
168
|
def self?.discovered_def_index_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[Symbol, untyped]
|
|
169
|
+
def self?.scan_summary_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[Symbol, untyped]
|
|
170
|
+
def self?.code_fingerprint: (String source, untyped comments) -> String
|
|
171
|
+
def self?.discovered_project_index_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[Symbol, untyped]
|
|
172
|
+
def self?.discovered_project_index_incremental: (Array[String] paths, seed_bundles: Hash[String, untyped], ?buffer: untyped) -> Hash[Symbol, untyped]
|
|
166
173
|
def self?.collect_class_decls: (untyped node, Array[String] qualified_prefix, Hash[String, Type::t] accumulator) -> void
|
|
167
174
|
def self?.qualified_name_for: (untyped constant_path_node) -> String?
|
|
168
175
|
def self?.render_constant_path: (untyped node) -> String
|
data/sig/rigor/plugin/base.rbs
CHANGED
|
@@ -28,8 +28,7 @@ class Rigor::Plugin::Base
|
|
|
28
28
|
|
|
29
29
|
def self.narrowing_facts: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
30
30
|
# Deprecating alias for `narrowing_facts` (ADR-80); removed in 0.3.0.
|
|
31
|
-
def self.
|
|
32
|
-
def self.type_specifiers: () -> Array[untyped]
|
|
31
|
+
def self.narrowing_facts_rules: () -> Array[untyped]
|
|
33
32
|
|
|
34
33
|
def self.suggest: (untyped name, untyped candidates) -> String?
|
|
35
34
|
|
|
@@ -48,7 +47,7 @@ class Rigor::Plugin::Base
|
|
|
48
47
|
# Engine-executed dispatch over the declared DSLs.
|
|
49
48
|
def node_rule_diagnostics: (path: untyped, scope: untyped, root: untyped) -> Array[untyped]
|
|
50
49
|
def dynamic_return_type: (call_node: untyped, scope: untyped, receiver_type: untyped) -> untyped
|
|
51
|
-
def
|
|
50
|
+
def narrowing_facts_for: (call_node: untyped, scope: untyped) -> Array[untyped]
|
|
52
51
|
|
|
53
52
|
# Authoring helpers.
|
|
54
53
|
def diagnostic: (untyped node, path: untyped, message: untyped, ?severity: untyped, ?rule: untyped, ?location: untyped) -> untyped
|
data/sig/rigor/reflection.rbs
CHANGED
|
@@ -6,6 +6,7 @@ module Rigor
|
|
|
6
6
|
def self?.nominal_for_name: (String | Symbol class_name, ?scope: Scope) -> Type::Nominal?
|
|
7
7
|
def self?.singleton_for_name: (String | Symbol class_name, ?scope: Scope) -> Type::Singleton?
|
|
8
8
|
def self?.constant_type_for: (String | Symbol constant_name, ?scope: Scope) -> Type::t?
|
|
9
|
+
def self?.resolve_constant_type: (String | Symbol name, ?scope: Scope) -> Type::t?
|
|
9
10
|
def self?.instance_method_definition: (String | Symbol class_name, String | Symbol method_name, ?scope: Scope) -> untyped
|
|
10
11
|
def self?.singleton_method_definition: (String | Symbol class_name, String | Symbol method_name, ?scope: Scope) -> untyped
|
|
11
12
|
def self?.instance_definition: (String | Symbol class_name, ?scope: Scope) -> untyped
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -11,6 +11,7 @@ module Rigor
|
|
|
11
11
|
attr_reader dynamic_origins: Hash[untyped, Symbol]
|
|
12
12
|
attr_reader local_origins: Hash[Symbol, Symbol]
|
|
13
13
|
attr_reader ivar_origins: Hash[Symbol, Symbol]
|
|
14
|
+
attr_reader void_origins: Hash[untyped, Inference::VoidOrigin]
|
|
14
15
|
attr_reader indexed_narrowings: Hash[IndexedKey, Type::t]
|
|
15
16
|
attr_reader method_chain_narrowings: Hash[ChainKey, Type::t]
|
|
16
17
|
attr_reader source_path: String?
|
|
@@ -28,6 +29,7 @@ module Rigor
|
|
|
28
29
|
def discovered_def_nodes: () -> Hash[String, Hash[Symbol, untyped]]
|
|
29
30
|
def discovered_singleton_def_nodes: () -> Hash[String, Hash[Symbol, untyped]]
|
|
30
31
|
def discovered_def_sources: () -> Hash[String, Hash[Symbol, String]]
|
|
32
|
+
def discovered_singleton_def_sources: () -> Hash[String, Hash[Symbol, String]]
|
|
31
33
|
def discovered_method_visibilities: () -> Hash[String, Hash[Symbol, Symbol]]
|
|
32
34
|
def discovered_superclasses: () -> Hash[String, String]
|
|
33
35
|
def discovered_includes: () -> Hash[String, Array[String]]
|
|
@@ -35,8 +37,11 @@ module Rigor
|
|
|
35
37
|
def data_member_layouts: () -> Hash[String, Array[Symbol]]
|
|
36
38
|
def struct_member_layouts: () -> Hash[String, { members: Array[Symbol], keyword_init: bool }]
|
|
37
39
|
def param_inferred_types: () -> Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
40
|
+
def run_generation: () -> Object?
|
|
38
41
|
def record_dynamic_origin: (untyped node, Symbol cause) -> Scope
|
|
39
42
|
|
|
43
|
+
def record_void_origin: (untyped node, Inference::VoidOrigin origin) -> Scope
|
|
44
|
+
|
|
40
45
|
class DiscoveryIndex
|
|
41
46
|
attr_reader declared_types: Hash[untyped, Type::t]
|
|
42
47
|
attr_reader class_ivars: Hash[String, Hash[Symbol, Type::t]]
|
|
@@ -48,6 +53,7 @@ module Rigor
|
|
|
48
53
|
attr_reader discovered_def_nodes: Hash[String, Hash[Symbol, untyped]]
|
|
49
54
|
attr_reader discovered_singleton_def_nodes: Hash[String, Hash[Symbol, untyped]]
|
|
50
55
|
attr_reader discovered_def_sources: Hash[String, Hash[Symbol, String]]
|
|
56
|
+
attr_reader discovered_singleton_def_sources: Hash[String, Hash[Symbol, String]]
|
|
51
57
|
attr_reader discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]]
|
|
52
58
|
attr_reader discovered_superclasses: Hash[String, String]
|
|
53
59
|
attr_reader discovered_includes: Hash[String, Array[String]]
|
|
@@ -55,10 +61,11 @@ module Rigor
|
|
|
55
61
|
attr_reader data_member_layouts: Hash[String, Array[Symbol]]
|
|
56
62
|
attr_reader struct_member_layouts: Hash[String, { members: Array[Symbol], keyword_init: bool }]
|
|
57
63
|
attr_reader param_inferred_types: Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
64
|
+
attr_reader run_generation: Object?
|
|
58
65
|
|
|
59
66
|
EMPTY: DiscoveryIndex
|
|
60
67
|
|
|
61
|
-
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
|
|
68
|
+
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_singleton_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]], ?run_generation: Object?) -> DiscoveryIndex
|
|
62
69
|
end
|
|
63
70
|
|
|
64
71
|
class IndexedKey
|
|
@@ -97,6 +104,9 @@ module Rigor
|
|
|
97
104
|
def with_declaration_sourced_local: (String | Symbol name, Type::t type) -> Scope
|
|
98
105
|
def with_local_declaration_mark: (String | Symbol name) -> Scope
|
|
99
106
|
def declaration_sourced?: (Symbol kind, String | Symbol name) -> bool
|
|
107
|
+
def with_inferred_param_mark: (String | Symbol name) -> Scope
|
|
108
|
+
def without_inferred_param_mark: (String | Symbol name) -> Scope
|
|
109
|
+
def inferred_param?: (String | Symbol name) -> bool
|
|
100
110
|
def forget_match_globals: () -> Scope
|
|
101
111
|
def class_ivars_for: (String | Symbol? class_name) -> Hash[Symbol, Type::t]
|
|
102
112
|
def class_cvars_for: (String | Symbol? class_name) -> Hash[Symbol, Type::t]
|
|
@@ -125,6 +135,7 @@ module Rigor
|
|
|
125
135
|
def local_facts: (String | Symbol name, ?bucket: Symbol?) -> Array[Analysis::FactStore::Fact]
|
|
126
136
|
def type_of: (untyped node, ?tracer: Inference::FallbackTracer?) -> Type::t
|
|
127
137
|
def evaluate: (untyped node, ?tracer: Inference::FallbackTracer?) -> [Type::t, Scope]
|
|
138
|
+
def user_method_return: (untyped def_node, Type::t receiver, Array[Type::t] arg_types) -> Type::t?
|
|
128
139
|
def join: (Scope other) -> Scope
|
|
129
140
|
def ==: (untyped other) -> bool
|
|
130
141
|
alias eql? ==
|
|
@@ -141,6 +141,16 @@ The exact recipe (the `BUNDLE_GEMFILE` wiring, the Dependabot entry) is in
|
|
|
141
141
|
role.
|
|
142
142
|
- **Determinism.** Add `--no-cache` in CI if you want each run independent
|
|
143
143
|
of any persisted `.rigor/cache`.
|
|
144
|
+
- **Cache persistence (opposite trade).** When the Rigor job's runtime
|
|
145
|
+
matters, persist `.rigor/cache` with the CI's cache facility. Current
|
|
146
|
+
Rigor detects CI and validates the restored cache by content hash
|
|
147
|
+
automatically (`cache.validation: auto`); on versions before the
|
|
148
|
+
`auto` default, **also set `RIGOR_STRICT_VALIDATION=1` on the job** —
|
|
149
|
+
without it a fresh checkout's regenerated stat tuples make the plugin
|
|
150
|
+
watch-glob cache slots read as stale on every run. A self-hosted
|
|
151
|
+
runner that reuses its workspace opts back into the faster stat check
|
|
152
|
+
with `cache.validation: stat`. Snippets: `rigor docs ci`
|
|
153
|
+
§ "Persisting the analysis cache across runs".
|
|
144
154
|
|
|
145
155
|
## Verify
|
|
146
156
|
|
|
@@ -89,6 +89,17 @@ The manual chapter covers:
|
|
|
89
89
|
- **Helix** (`languages.toml`)
|
|
90
90
|
- **Emacs** (Eglot and lsp-mode)
|
|
91
91
|
|
|
92
|
+
**The editors are not equally cheap to wire.** Neovim / Helix / Emacs
|
|
93
|
+
are plain config snippets. **VS Code has no first-party Rigor
|
|
94
|
+
extension yet** — its path means authoring and side-loading a minimal
|
|
95
|
+
extension (a `LanguageClient` wrapper), which is a development task,
|
|
96
|
+
not a `settings.json` edit. Say so *before* starting: the developer
|
|
97
|
+
may prefer to wait for a first-class extension, and an autonomous
|
|
98
|
+
agent should treat the VS Code path as a **user handoff** (report the
|
|
99
|
+
manual's recipe; do not scaffold an unrequested extension). Phase 4's
|
|
100
|
+
verification (live diagnostics, hover) also requires a human in front
|
|
101
|
+
of the editor — an agent stops there and reports, for every editor.
|
|
102
|
+
|
|
92
103
|
### Phase 3 — apply the matching config
|
|
93
104
|
|
|
94
105
|
Open the manual's **Editor wiring** section and apply the snippet for
|