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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../source/constant_path"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
# ADR-48 Struct follow-up, slice 3 — the fold-safe-local scan. Determines,
|
|
10
|
+
# for a single local-variable scope (a method body or the program
|
|
11
|
+
# top-level), which `Struct`-materialised locals are **provably never
|
|
12
|
+
# mutated, aliased, or escaped** and so may have their member reads folded
|
|
13
|
+
# off a *stored* binding (relaxing the slice-2 fresh-receiver gate).
|
|
14
|
+
#
|
|
15
|
+
# The analysis is a conservative ALLOW-LIST, not a deny-list: a local is
|
|
16
|
+
# fold-safe only when *every* read of it is the receiver of a known-pure
|
|
17
|
+
# read call. Anything the scan does not recognise as a pure read — a
|
|
18
|
+
# setter, an `[]=` / operator-write, an argument / alias / container store
|
|
19
|
+
# / return (escape), or an unknown method call (which could mutate `self`
|
|
20
|
+
# internally) — disqualifies the local. A missed case therefore makes the
|
|
21
|
+
# scan over-conservative (no fold), **never unsound** (folding a mutated
|
|
22
|
+
# value) — the false-positive-safe direction.
|
|
23
|
+
#
|
|
24
|
+
# Soundness rests on a counting identity: a local `n` is fold-safe iff
|
|
25
|
+
# *every* `LocalVariableReadNode(n)` is the receiver of a pure-read call.
|
|
26
|
+
# Equivalently `total_reads(n) == pure_receiver_reads(n)`. Any other
|
|
27
|
+
# occurrence — `n` as a setter receiver (`n.x = v` is a `:x=` call, not a
|
|
28
|
+
# pure read), an `[]=`/operator-write receiver (the receiver read is not
|
|
29
|
+
# under a pure call), a call argument, an assignment RHS (alias), a
|
|
30
|
+
# container element, a bare value (return/escape) — leaves a read that is
|
|
31
|
+
# not a pure-receiver read, so the counts diverge.
|
|
32
|
+
#
|
|
33
|
+
# See docs/notes/20260615-struct-folding-slice3-design.md and
|
|
34
|
+
# docs/adr/48-data-struct-value-folding.md § "Struct follow-up".
|
|
35
|
+
module StructFoldSafety
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
EMPTY = Set.new.freeze
|
|
39
|
+
|
|
40
|
+
# The fixed `Struct` read methods that never mutate. A member-reader name
|
|
41
|
+
# (`:x`) is added per-local from the local's recorded layout. A setter
|
|
42
|
+
# (`:x=`), `:[]=`, `store`, `push`, etc. are deliberately absent.
|
|
43
|
+
FIXED_READS = %i[
|
|
44
|
+
[] dig to_h to_hash to_a values members deconstruct deconstruct_keys
|
|
45
|
+
== != eql? equal? hash inspect to_s size length frozen? each each_pair
|
|
46
|
+
values_at with
|
|
47
|
+
].to_set.freeze
|
|
48
|
+
|
|
49
|
+
# Nested `def` / `class` / `module` bodies open a *new* local-variable
|
|
50
|
+
# scope, so the scan does not descend into them — a local of the same
|
|
51
|
+
# name there is a different binding. Blocks share the enclosing locals
|
|
52
|
+
# (closures), so the scan does descend into them.
|
|
53
|
+
def scope_boundary?(node)
|
|
54
|
+
node.is_a?(Prism::DefNode) || node.is_a?(Prism::ClassNode) ||
|
|
55
|
+
node.is_a?(Prism::ModuleNode) || node.is_a?(Prism::SingletonClassNode)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param root [Prism::Node, nil] the local-variable scope to scan.
|
|
59
|
+
# @param layout_lookup [#call] a `String -> Array[Symbol] | nil` resolver
|
|
60
|
+
# mapping a constant receiver name to its struct member list.
|
|
61
|
+
# @return [Set<Symbol>] the fold-safe local names.
|
|
62
|
+
def fold_safe_locals(root, layout_lookup)
|
|
63
|
+
return EMPTY if root.nil?
|
|
64
|
+
|
|
65
|
+
members = {}
|
|
66
|
+
writes = Hash.new(0)
|
|
67
|
+
collect_struct_locals(root, layout_lookup, members, writes)
|
|
68
|
+
return EMPTY if members.empty?
|
|
69
|
+
|
|
70
|
+
total = Hash.new(0)
|
|
71
|
+
pure = Hash.new(0)
|
|
72
|
+
count_uses(root, members, total, pure)
|
|
73
|
+
|
|
74
|
+
safe = members.each_key.select do |name|
|
|
75
|
+
writes[name] == 1 && total[name].positive? && total[name] == pure[name]
|
|
76
|
+
end
|
|
77
|
+
safe.empty? ? EMPTY : safe.to_set
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Pass 1 — record each local's single struct materialisation (its member
|
|
81
|
+
# set) and count its assignments. A local assigned more than once is
|
|
82
|
+
# later excluded (the static fold-safe set cannot track a rebinding).
|
|
83
|
+
def collect_struct_locals(node, layout_lookup, members, writes)
|
|
84
|
+
return if node.nil?
|
|
85
|
+
|
|
86
|
+
if node.is_a?(Prism::LocalVariableWriteNode)
|
|
87
|
+
writes[node.name] += 1
|
|
88
|
+
found = struct_materialization_members(node.value, layout_lookup)
|
|
89
|
+
members[node.name] = found if found
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
each_local_scope_child(node) do |child|
|
|
93
|
+
collect_struct_locals(child, layout_lookup, members, writes)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Pass 2 — count, per recorded struct local, total reads vs. reads that
|
|
98
|
+
# are the receiver of a pure-read call.
|
|
99
|
+
def count_uses(node, members, total, pure)
|
|
100
|
+
return if node.nil?
|
|
101
|
+
|
|
102
|
+
total[node.name] += 1 if node.is_a?(Prism::LocalVariableReadNode) && members.key?(node.name)
|
|
103
|
+
|
|
104
|
+
if node.is_a?(Prism::CallNode)
|
|
105
|
+
receiver = node.receiver
|
|
106
|
+
if receiver.is_a?(Prism::LocalVariableReadNode) && members.key?(receiver.name) &&
|
|
107
|
+
pure_read_call?(node, members[receiver.name])
|
|
108
|
+
pure[receiver.name] += 1
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
each_local_scope_child(node) do |child|
|
|
113
|
+
count_uses(child, members, total, pure)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# A call is a pure read of the receiver when its name is a fixed Struct
|
|
118
|
+
# read or one of the receiver's member readers. Setters (`:x=`), `:[]=`,
|
|
119
|
+
# and any unknown method are excluded.
|
|
120
|
+
def pure_read_call?(call_node, member_set)
|
|
121
|
+
name = call_node.name
|
|
122
|
+
FIXED_READS.include?(name) || member_set.include?(name)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# The member set of a `<Struct chain>.new(...)` / `.[]` materialisation,
|
|
126
|
+
# or nil. Handles the inline `Struct.new(:a, :b).new(...)` form and the
|
|
127
|
+
# `Const.new(...)` form (resolved through the layout side-table).
|
|
128
|
+
def struct_materialization_members(value_node, layout_lookup)
|
|
129
|
+
return nil unless value_node.is_a?(Prism::CallNode)
|
|
130
|
+
return nil unless %i[new []].include?(value_node.name)
|
|
131
|
+
|
|
132
|
+
receiver = value_node.receiver
|
|
133
|
+
case receiver
|
|
134
|
+
when Prism::CallNode
|
|
135
|
+
struct_new_member_set(receiver)
|
|
136
|
+
|
|
137
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
138
|
+
name = Source::ConstantPath.qualified_name_or_nil(receiver)
|
|
139
|
+
name && member_set_of(layout_lookup.call(name))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# The Symbol member set of a literal `Struct.new(:a, :b [, keyword_init:])`
|
|
144
|
+
# call, or nil. (A leading String name and the trailing options hash are
|
|
145
|
+
# ignored — only the literal-Symbol positionals contribute.)
|
|
146
|
+
def struct_new_member_set(call_node)
|
|
147
|
+
return nil unless call_node.is_a?(Prism::CallNode) && call_node.name == :new
|
|
148
|
+
return nil unless meta_constant?(call_node.receiver, :Struct)
|
|
149
|
+
|
|
150
|
+
args = call_node.arguments&.arguments || []
|
|
151
|
+
positional = args.last.is_a?(Prism::KeywordHashNode) ? args[0..-2] : args
|
|
152
|
+
positional = positional[1..] if positional.first.is_a?(Prism::StringNode)
|
|
153
|
+
return nil if positional.nil? || positional.empty?
|
|
154
|
+
return nil unless positional.all?(Prism::SymbolNode)
|
|
155
|
+
|
|
156
|
+
positional.to_set { |sym| sym.unescaped.to_sym }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def member_set_of(members)
|
|
160
|
+
members && !members.empty? ? members.to_set : nil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def meta_constant?(node, name)
|
|
164
|
+
case node
|
|
165
|
+
when Prism::ConstantReadNode then node.name == name
|
|
166
|
+
when Prism::ConstantPathNode then node.parent.nil? && node.name == name
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Yields each child to recurse into, skipping the subtree of a nested
|
|
171
|
+
# local-variable-scope boundary (a `def` / `class` / `module`).
|
|
172
|
+
def each_local_scope_child(node)
|
|
173
|
+
node.compact_child_nodes.each do |child|
|
|
174
|
+
next if scope_boundary?(child)
|
|
175
|
+
|
|
176
|
+
yield child
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -8,13 +8,13 @@ module Rigor
|
|
|
8
8
|
# the template name. Stored in {SyntheticMethodIndex} and
|
|
9
9
|
# consulted by {MethodDispatcher} below the RBS dispatch tier.
|
|
10
10
|
#
|
|
11
|
-
# Per ADR-16 § WD13 (cost-bounded best-effort):
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# string
|
|
16
|
-
#
|
|
17
|
-
#
|
|
11
|
+
# Per ADR-16 § WD13 (cost-bounded best-effort): method names
|
|
12
|
+
# emit with return types promoted by the dispatcher's Slice 6
|
|
13
|
+
# tiers — Slice 6a promotes via `origin_module` (TierB) and
|
|
14
|
+
# Slice 6b via `return_type` nominal lookup (TierC). The
|
|
15
|
+
# `return_type` string is preserved so promotion can resolve
|
|
16
|
+
# it without re-walking; `"untyped"` / `"void"` placeholders
|
|
17
|
+
# fall back to `Dynamic[Top]`.
|
|
18
18
|
#
|
|
19
19
|
# The `provenance` Hash carries debug / `--explain` metadata:
|
|
20
20
|
# plugin id, the template's call shape, and the source
|
|
@@ -228,7 +228,7 @@ module Rigor
|
|
|
228
228
|
return unless call.is_a?(Prism::CallNode) && call.receiver.nil? && call.name == template.variant_method
|
|
229
229
|
|
|
230
230
|
args = call.arguments&.arguments || []
|
|
231
|
-
variant_const = const_name_string(args[template.
|
|
231
|
+
variant_const = const_name_string(args[template.symbol_arg_position])
|
|
232
232
|
return if variant_const.nil?
|
|
233
233
|
|
|
234
234
|
yield variant_const, args[template.inner_arg_position]
|
|
@@ -21,20 +21,14 @@ require_relative "../type/hash_shape"
|
|
|
21
21
|
|
|
22
22
|
module Rigor
|
|
23
23
|
module LanguageServer
|
|
24
|
-
# Answers `textDocument/completion` requests.
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# Constant-path completion (slice 6), Union / Intersection /
|
|
31
|
-
# Refined / Shape receiver handling (slice 7), and parse-recovery
|
|
32
|
-
# fallback for malformed buffers (slice 8) extend this v1 floor.
|
|
24
|
+
# Answers `textDocument/completion` requests. Provides method
|
|
25
|
+
# completion for `obj.|` (known-type receiver → RBS-known methods),
|
|
26
|
+
# constant-path completion, hash-key completion, Union / Intersection /
|
|
27
|
+
# Refined / Shape receiver handling, and parse-recovery fallback for
|
|
28
|
+
# malformed buffers (sentinel injection).
|
|
33
29
|
#
|
|
34
30
|
# LSP `CompletionItemKind` values used:
|
|
35
|
-
# - 2 = Method
|
|
36
|
-
#
|
|
37
|
-
# Slice 6 will add 7 (Class), 9 (Module), 21 (Constant).
|
|
31
|
+
# - 2 = Method, 5 = Field, 7 = Class, 9 = Module, 21 = Constant.
|
|
38
32
|
class CompletionProvider # rubocop:disable Metrics/ClassLength
|
|
39
33
|
include BufferResolution
|
|
40
34
|
|
|
@@ -15,10 +15,10 @@ module Rigor
|
|
|
15
15
|
# a `BufferBinding` from the BufferTable entry, runs the Runner,
|
|
16
16
|
# and pushes the resulting LSP `Diagnostic[]` through the writer.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
18
|
+
# Debouncing is wired via an optional `Debouncer` injected at
|
|
19
|
+
# construction (delay defaults to 200ms quiet-time); without a
|
|
20
|
+
# debouncer each call blocks synchronously (primarily for specs).
|
|
21
|
+
# Ractor-pool dispatch is queued.
|
|
22
22
|
class DiagnosticPublisher
|
|
23
23
|
# Maps Rigor severity symbols to LSP DiagnosticSeverity
|
|
24
24
|
# integers per spec § "Diagnostic":
|
|
@@ -128,9 +128,9 @@ module Rigor
|
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
# LSP `Range` is 0-based start + end with `character` in
|
|
131
|
-
# UTF-16 code units.
|
|
132
|
-
# ASCII source); UTF-16 conversion stays queued
|
|
133
|
-
# doc § "Open questions".
|
|
131
|
+
# UTF-16 code units. This implementation emits byte columns
|
|
132
|
+
# (correct for ASCII source); UTF-16 conversion stays queued
|
|
133
|
+
# per design doc § "Open questions".
|
|
134
134
|
def range_from(location)
|
|
135
135
|
{
|
|
136
136
|
start: { line: location.start_line - 1, character: location.start_column },
|
|
@@ -64,9 +64,8 @@ module Rigor
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def base_scope(_path)
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# every cursor stop.
|
|
67
|
+
# Pulls the Environment from the cached ProjectContext so
|
|
68
|
+
# each hover avoids repeating the RBS-load build.
|
|
70
69
|
Scope.empty(environment: @project_context.environment)
|
|
71
70
|
end
|
|
72
71
|
end
|
|
@@ -11,17 +11,8 @@ module Rigor
|
|
|
11
11
|
module LanguageServer
|
|
12
12
|
# Builds the LSP `Hover.contents` markdown body. Dispatches on
|
|
13
13
|
# the hovered Prism node class so each shape (method call,
|
|
14
|
-
# constant, local,
|
|
15
|
-
# type-aware presentation
|
|
16
|
-
#
|
|
17
|
-
# Slice A1 (this commit) ships:
|
|
18
|
-
# - default body bit-for-bit matching the LSP v1 slice 5
|
|
19
|
-
# output (`type:` / `erased:` / `node:`),
|
|
20
|
-
# - `Prism::CallNode` specialisation surfacing the receiver
|
|
21
|
-
# type + RBS-erased method signature.
|
|
22
|
-
#
|
|
23
|
-
# Slices A2-A4 extend the dispatch with constant /
|
|
24
|
-
# local / ivar / literal renderers per
|
|
14
|
+
# constant, local, ivar, literal) gets the most relevant
|
|
15
|
+
# type-aware presentation per
|
|
25
16
|
# `docs/design/20260517-lsp-hover-completion.md`.
|
|
26
17
|
class HoverRenderer
|
|
27
18
|
# @param node_scope_lookup [#[]] node-to-scope table built
|
|
@@ -6,21 +6,13 @@ require_relative "buffer_table"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module LanguageServer
|
|
8
8
|
# LSP server lifecycle state machine + JSON-RPC method dispatcher.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# -
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# notifications). Out-of-state requests return the
|
|
17
|
-
# spec-defined `InvalidRequest` (-32002) / `MethodNotFound`
|
|
18
|
-
# (-32601) error shapes.
|
|
19
|
-
#
|
|
20
|
-
# Slice 2 wraps this dispatcher in a stdio JSON-RPC reader /
|
|
21
|
-
# writer so the CLI subcommand can serve real LSP clients.
|
|
22
|
-
# Slice 3+ adds document sync; slice 4+ adds publishDiagnostics;
|
|
23
|
-
# slice 5-8 add the rest of the v1 capability surface.
|
|
9
|
+
# State machine: `:uninitialized` → `:initialized` → `:shutdown`
|
|
10
|
+
# → `:exited`. {#dispatch} routes (method, params) to the matching
|
|
11
|
+
# handler and returns the response payload (`nil` for notifications);
|
|
12
|
+
# out-of-state requests return `InvalidRequest` (-32002) /
|
|
13
|
+
# `MethodNotFound` (-32601). Full v1 capability surface (document
|
|
14
|
+
# sync, publishDiagnostics, hover, completion, sig-help, folding,
|
|
15
|
+
# selection, and watched-file invalidation) is implemented.
|
|
24
16
|
class Server # rubocop:disable Metrics/ClassLength
|
|
25
17
|
# JSON-RPC error codes per LSP spec § "Response Message".
|
|
26
18
|
ERROR_PARSE_ERROR = -32_700
|
|
@@ -58,8 +50,8 @@ module Rigor
|
|
|
58
50
|
# the providers read on every request. When present,
|
|
59
51
|
# `workspace/didChangeWatchedFiles` and
|
|
60
52
|
# `workspace/didChangeConfiguration` invalidate the cache;
|
|
61
|
-
# nil means "no project context"
|
|
62
|
-
#
|
|
53
|
+
# nil means "no project context": each request rebuilds env
|
|
54
|
+
# from scratch (mainly for specs and backward compatibility).
|
|
63
55
|
def initialize(buffer_table: BufferTable.new, publisher: nil, # rubocop:disable Metrics/ParameterLists
|
|
64
56
|
hover_provider: nil, document_symbol_provider: nil,
|
|
65
57
|
completion_provider: nil, signature_help_provider: nil,
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
# The Language Server subsystem. See
|
|
5
5
|
# `docs/design/20260517-language-server.md` for the design.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# surface.
|
|
6
|
+
# The full v1 capability surface (document sync, publishDiagnostics,
|
|
7
|
+
# hover, completion, sig-help, folding, selection, and watched-file
|
|
8
|
+
# invalidation) is implemented. This module is the namespace and
|
|
9
|
+
# require entry point for the subsystem.
|
|
11
10
|
module LanguageServer
|
|
12
11
|
end
|
|
13
12
|
end
|