rigortype 0.1.4 → 0.1.6
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 +69 -56
- data/lib/rigor/analysis/buffer_binding.rb +36 -0
- data/lib/rigor/analysis/check_rules.rb +11 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +14 -1
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +105 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +32 -12
- data/lib/rigor/analysis/fact_store.rb +15 -3
- data/lib/rigor/analysis/project_scan.rb +39 -0
- data/lib/rigor/analysis/result.rb +11 -3
- data/lib/rigor/analysis/run_stats.rb +193 -0
- data/lib/rigor/analysis/runner.rb +681 -19
- data/lib/rigor/analysis/worker_session.rb +339 -0
- data/lib/rigor/builtins/hkt_builtins.rb +342 -0
- data/lib/rigor/builtins/imported_refinements.rb +6 -2
- data/lib/rigor/builtins/regex_refinement.rb +17 -12
- data/lib/rigor/builtins/static_return_refinements.rb +120 -0
- data/lib/rigor/cache/rbs_descriptor.rb +3 -1
- data/lib/rigor/cache/store.rb +72 -9
- data/lib/rigor/cli/lsp_command.rb +129 -0
- data/lib/rigor/cli/type_of_command.rb +44 -5
- data/lib/rigor/cli.rb +122 -10
- data/lib/rigor/configuration.rb +168 -7
- data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
- data/lib/rigor/environment/class_registry.rb +12 -3
- data/lib/rigor/environment/hkt_registry_holder.rb +33 -0
- data/lib/rigor/environment/lockfile_resolver.rb +125 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
- data/lib/rigor/environment/rbs_loader.rb +238 -7
- data/lib/rigor/environment/reflection.rb +152 -0
- data/lib/rigor/environment/reporters.rb +40 -0
- data/lib/rigor/environment.rb +179 -10
- data/lib/rigor/inference/acceptance.rb +83 -4
- data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
- data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
- data/lib/rigor/inference/expression_typer.rb +59 -2
- data/lib/rigor/inference/hkt_body.rb +171 -0
- data/lib/rigor/inference/hkt_body_parser.rb +363 -0
- data/lib/rigor/inference/hkt_reducer.rb +256 -0
- data/lib/rigor/inference/hkt_registry.rb +223 -0
- data/lib/rigor/inference/macro_block_self_type.rb +96 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -29
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +18 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +126 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +87 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -40
- data/lib/rigor/inference/method_dispatcher.rb +282 -6
- data/lib/rigor/inference/method_parameter_binder.rb +21 -11
- data/lib/rigor/inference/narrowing.rb +127 -8
- data/lib/rigor/inference/project_patched_methods.rb +70 -0
- data/lib/rigor/inference/project_patched_scanner.rb +210 -0
- data/lib/rigor/inference/scope_indexer.rb +156 -12
- data/lib/rigor/inference/statement_evaluator.rb +106 -6
- data/lib/rigor/inference/synthetic_method.rb +86 -0
- data/lib/rigor/inference/synthetic_method_index.rb +82 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +599 -0
- data/lib/rigor/language_server/buffer_table.rb +63 -0
- data/lib/rigor/language_server/completion_provider.rb +438 -0
- data/lib/rigor/language_server/debouncer.rb +86 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +167 -0
- data/lib/rigor/language_server/document_symbol_provider.rb +142 -0
- data/lib/rigor/language_server/folding_range_provider.rb +75 -0
- data/lib/rigor/language_server/hover_provider.rb +74 -0
- data/lib/rigor/language_server/hover_renderer.rb +312 -0
- data/lib/rigor/language_server/loop.rb +71 -0
- data/lib/rigor/language_server/project_context.rb +145 -0
- data/lib/rigor/language_server/selection_range_provider.rb +93 -0
- data/lib/rigor/language_server/server.rb +384 -0
- data/lib/rigor/language_server/signature_help_provider.rb +249 -0
- data/lib/rigor/language_server/synchronized_writer.rb +28 -0
- data/lib/rigor/language_server/uri.rb +40 -0
- data/lib/rigor/language_server.rb +29 -0
- data/lib/rigor/plugin/base.rb +63 -0
- data/lib/rigor/plugin/blueprint.rb +60 -0
- data/lib/rigor/plugin/loader.rb +3 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
- data/lib/rigor/plugin/macro/external_file.rb +143 -0
- data/lib/rigor/plugin/macro/heredoc_template.rb +315 -0
- data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
- data/lib/rigor/plugin/macro.rb +31 -0
- data/lib/rigor/plugin/manifest.rb +127 -9
- data/lib/rigor/plugin/registry.rb +51 -2
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/hkt_directives.rb +326 -0
- data/lib/rigor/rbs_extended.rb +82 -2
- data/lib/rigor/sig_gen/generator.rb +12 -3
- data/lib/rigor/trinary.rb +15 -11
- data/lib/rigor/type/app.rb +107 -0
- data/lib/rigor/type/bot.rb +6 -3
- data/lib/rigor/type/combinator.rb +12 -1
- data/lib/rigor/type/integer_range.rb +7 -7
- data/lib/rigor/type/refined.rb +18 -12
- data/lib/rigor/type/top.rb +4 -3
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +7 -1
- data/lib/rigor/type_node/identifier.rb +9 -1
- data/lib/rigor/type_node/string_literal.rb +4 -1
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/environment.rbs +11 -4
- data/sig/rigor/inference.rbs +2 -0
- data/sig/rigor/plugin/blueprint.rbs +7 -0
- data/sig/rigor/plugin/manifest.rbs +1 -1
- data/sig/rigor/plugin/registry.rbs +14 -1
- data/sig/rigor.rbs +37 -2
- metadata +92 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../environment"
|
|
6
|
+
require_relative "../scope"
|
|
7
|
+
require_relative "../cache/store"
|
|
8
|
+
require_relative "../plugin"
|
|
9
|
+
require_relative "../rbs_extended/reporter"
|
|
10
|
+
require_relative "../reflection"
|
|
11
|
+
require_relative "../type/combinator"
|
|
12
|
+
require_relative "../inference/coverage_scanner"
|
|
13
|
+
require_relative "../inference/scope_indexer"
|
|
14
|
+
require_relative "../inference/method_dispatcher/file_folding"
|
|
15
|
+
require_relative "check_rules"
|
|
16
|
+
require_relative "dependency_source_inference"
|
|
17
|
+
require_relative "diagnostic"
|
|
18
|
+
|
|
19
|
+
module Rigor
|
|
20
|
+
module Analysis
|
|
21
|
+
# ADR-15 Phase 4a — per-worker analysis substrate.
|
|
22
|
+
# [ADR-15](../../../docs/adr/15-ractor-concurrency.md)
|
|
23
|
+
# § Phase 4 carves the eventual Ractor-isolated worker pool
|
|
24
|
+
# into three sub-phases; this is the substrate that 4b will
|
|
25
|
+
# wrap in `Ractor.new` and 4c will gate behind
|
|
26
|
+
# `RIGOR_RACTOR_WORKERS`. NO Ractor in the loop yet — 4a
|
|
27
|
+
# exists so the per-worker ownership boundary is testable in
|
|
28
|
+
# the absence of any Ractor coordination.
|
|
29
|
+
#
|
|
30
|
+
# The constructor takes only `Ractor.shareable?` inputs:
|
|
31
|
+
#
|
|
32
|
+
# - `configuration` — Phase 2a ({Rigor::Configuration} is
|
|
33
|
+
# `Ractor.shareable?`).
|
|
34
|
+
# - `cache_store` — frozen-shareable handle is NOT a precondition;
|
|
35
|
+
# future 4b workers build their OWN Store at the shared
|
|
36
|
+
# `cache_root` directory. 4a accepts an already-built Store
|
|
37
|
+
# for the no-Ractor coordinator path.
|
|
38
|
+
# - `plugin_blueprints` — Phase 3a
|
|
39
|
+
# (`Array<Plugin::Blueprint>` is `Ractor.shareable?`).
|
|
40
|
+
# - `explain` — Boolean.
|
|
41
|
+
#
|
|
42
|
+
# Internally the session OWNS (and never shares):
|
|
43
|
+
#
|
|
44
|
+
# - {Rigor::Plugin::Services} bound to the per-worker Store.
|
|
45
|
+
# - {Rigor::Plugin::Registry} materialised from the blueprints
|
|
46
|
+
# via {Rigor::Plugin::Registry.materialize}; each plugin
|
|
47
|
+
# instance, with its mutable per-run accumulators
|
|
48
|
+
# (`@reachable_absurd_nodes`, `*_index`, …) lives entirely
|
|
49
|
+
# inside this session.
|
|
50
|
+
# - {Rigor::RbsExtended::Reporter} +
|
|
51
|
+
# {Rigor::Analysis::DependencySourceInference::BoundaryCrossReporter}
|
|
52
|
+
# (Mutex-bearing; intentionally per-worker — the runner
|
|
53
|
+
# merges entries post-pool via {#drain_reporters}).
|
|
54
|
+
# - {Rigor::Environment} threaded with the per-worker reporters
|
|
55
|
+
# so reporter writes from inference / dispatcher accumulate
|
|
56
|
+
# into the worker's own state.
|
|
57
|
+
#
|
|
58
|
+
# Plugin `prepare` runs ONCE at construction time so each
|
|
59
|
+
# worker is "warm" by the time `#analyze` is first called. Any
|
|
60
|
+
# raise from `prepare` is captured into {#prepare_diagnostics}
|
|
61
|
+
# so the runner can surface them alongside the per-file
|
|
62
|
+
# diagnostic stream.
|
|
63
|
+
#
|
|
64
|
+
# Equivalence contract (proven by spec): given identical
|
|
65
|
+
# `(configuration, cache_store, plugin_blueprints)`, the
|
|
66
|
+
# multiset of diagnostics from
|
|
67
|
+
# `paths.flat_map { |p| session.analyze(p) }` plus
|
|
68
|
+
# {#prepare_diagnostics} plus reporter drains MUST equal the
|
|
69
|
+
# corresponding subset of {Rigor::Analysis::Runner#run}'s
|
|
70
|
+
# output (modulo severity-profile re-stamping, which the
|
|
71
|
+
# session leaves to the caller because it is a per-run
|
|
72
|
+
# aggregate concern).
|
|
73
|
+
class WorkerSession
|
|
74
|
+
attr_reader :configuration, :cache_store, :services, :plugin_registry,
|
|
75
|
+
:dependency_source_index, :environment,
|
|
76
|
+
:rbs_extended_reporter, :boundary_cross_reporter,
|
|
77
|
+
:prepare_diagnostics
|
|
78
|
+
|
|
79
|
+
# @param configuration [Rigor::Configuration]
|
|
80
|
+
# @param cache_store [Rigor::Cache::Store, nil] persistent
|
|
81
|
+
# cache the session exposes to plugin-side producers and
|
|
82
|
+
# the RBS loader. Pass `nil` to disable caching.
|
|
83
|
+
# @param plugin_blueprints [Array<Rigor::Plugin::Blueprint>]
|
|
84
|
+
# replay descriptors. Empty array yields a session with
|
|
85
|
+
# no plugin contributions.
|
|
86
|
+
# @param explain [Boolean] when true, `#analyze` additionally
|
|
87
|
+
# emits one `:info` `fallback` diagnostic per
|
|
88
|
+
# directly-unrecognised node, mirroring
|
|
89
|
+
# {Rigor::Analysis::Runner#explain_diagnostics}.
|
|
90
|
+
def initialize(configuration:, cache_store: nil, # rubocop:disable Metrics/MethodLength
|
|
91
|
+
plugin_blueprints: [], explain: false, buffer: nil)
|
|
92
|
+
@configuration = configuration
|
|
93
|
+
@cache_store = cache_store
|
|
94
|
+
@explain = explain
|
|
95
|
+
@buffer = buffer
|
|
96
|
+
|
|
97
|
+
# NOTE: `Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths`
|
|
98
|
+
# is process-global state. Writing it from a non-main
|
|
99
|
+
# Ractor would raise `Ractor::IsolationError`, so the
|
|
100
|
+
# session does NOT touch it — the CALLER (typically
|
|
101
|
+
# {Rigor::Analysis::Runner#run}) is responsible for
|
|
102
|
+
# setting it on the main Ractor before spawning the
|
|
103
|
+
# pool. The substrate stays Ractor-safe by construction.
|
|
104
|
+
@rbs_extended_reporter = RbsExtended::Reporter.new
|
|
105
|
+
@boundary_cross_reporter = DependencySourceInference::BoundaryCrossReporter.new
|
|
106
|
+
@dependency_source_index = DependencySourceInference::Builder.build(configuration.dependencies)
|
|
107
|
+
|
|
108
|
+
@services = Plugin::Services.new(
|
|
109
|
+
reflection: Reflection,
|
|
110
|
+
type: Type::Combinator,
|
|
111
|
+
configuration: configuration,
|
|
112
|
+
cache_store: cache_store,
|
|
113
|
+
trust_policy: build_trust_policy
|
|
114
|
+
)
|
|
115
|
+
@plugin_registry = Plugin::Registry.materialize(
|
|
116
|
+
blueprints: plugin_blueprints, services: @services
|
|
117
|
+
)
|
|
118
|
+
@environment = Environment.for_project(
|
|
119
|
+
libraries: configuration.libraries,
|
|
120
|
+
signature_paths: configuration.signature_paths,
|
|
121
|
+
cache_store: cache_store,
|
|
122
|
+
plugin_registry: @plugin_registry,
|
|
123
|
+
dependency_source_index: @dependency_source_index,
|
|
124
|
+
rbs_extended_reporter: @rbs_extended_reporter,
|
|
125
|
+
boundary_cross_reporter: @boundary_cross_reporter,
|
|
126
|
+
bundler_bundle_path: configuration.bundler_bundle_path,
|
|
127
|
+
bundler_auto_detect: configuration.bundler_auto_detect,
|
|
128
|
+
bundler_lockfile: configuration.bundler_lockfile,
|
|
129
|
+
rbs_collection_lockfile: configuration.rbs_collection_lockfile,
|
|
130
|
+
rbs_collection_auto_detect: configuration.rbs_collection_auto_detect
|
|
131
|
+
)
|
|
132
|
+
@prepare_diagnostics = run_plugin_prepare.freeze
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Equivalent of {Rigor::Analysis::Runner#analyze_file} +
|
|
136
|
+
# `plugin_emitted_diagnostics` + `explain_diagnostics`.
|
|
137
|
+
# Returns a flat `Array<Diagnostic>` for the file. Severity
|
|
138
|
+
# profile re-stamping is intentionally NOT applied — that
|
|
139
|
+
# is a per-run aggregate concern handled by the caller.
|
|
140
|
+
def analyze(path)
|
|
141
|
+
parse_result = parse_source(path)
|
|
142
|
+
return parse_diagnostics(path, parse_result) unless parse_result.errors.empty?
|
|
143
|
+
|
|
144
|
+
scope = Scope.empty(environment: @environment, source_path: path)
|
|
145
|
+
index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope)
|
|
146
|
+
diagnostics = CheckRules.diagnose(
|
|
147
|
+
path: path,
|
|
148
|
+
root: parse_result.value,
|
|
149
|
+
scope_index: index,
|
|
150
|
+
comments: parse_result.comments,
|
|
151
|
+
disabled_rules: @configuration.disabled_rules
|
|
152
|
+
)
|
|
153
|
+
diagnostics += plugin_emitted_diagnostics(path, parse_result.value, scope)
|
|
154
|
+
diagnostics + explain_diagnostics(path, parse_result.value, scope)
|
|
155
|
+
rescue Errno::ENOENT => e
|
|
156
|
+
[analyzer_error(path, e.message)]
|
|
157
|
+
rescue StandardError => e
|
|
158
|
+
[analyzer_error(path, "internal analyzer error: #{e.class}: #{e.message}")]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Read-once snapshot of the per-worker reporters so the
|
|
162
|
+
# caller (or the eventual Phase 4b pool aggregator) can
|
|
163
|
+
# merge into a single coordinator-side reporter. Both
|
|
164
|
+
# reporters dedupe at write time, so a post-hoc concat +
|
|
165
|
+
# de-dup at the entry-key level is sound.
|
|
166
|
+
def drain_reporters
|
|
167
|
+
{
|
|
168
|
+
rbs_extended: {
|
|
169
|
+
unresolved_payloads: @rbs_extended_reporter.unresolved_payloads,
|
|
170
|
+
lossy_projections: @rbs_extended_reporter.lossy_projections
|
|
171
|
+
},
|
|
172
|
+
boundary_cross: @boundary_cross_reporter.entries
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
# See {Runner#parse_source}. Same contract: if `@buffer`
|
|
179
|
+
# binds `path` to a physical file, read the physical bytes
|
|
180
|
+
# but stamp the parse buffer's `filepath:` as the LOGICAL
|
|
181
|
+
# path so downstream diagnostics carry the logical path.
|
|
182
|
+
def parse_source(path)
|
|
183
|
+
physical = @buffer ? @buffer.resolve(path) : path
|
|
184
|
+
return Prism.parse_file(physical, version: @configuration.target_ruby) if physical == path
|
|
185
|
+
|
|
186
|
+
Prism.parse(File.read(physical), filepath: path, version: @configuration.target_ruby)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Mirrors {Runner#build_trust_policy}. Workers under Phase
|
|
190
|
+
# 4b will need the same trust derivation, and the
|
|
191
|
+
# configuration is already shareable, so deriving it inside
|
|
192
|
+
# the session keeps the substrate decoupled from the
|
|
193
|
+
# coordinator's helper.
|
|
194
|
+
def build_trust_policy
|
|
195
|
+
trusted_gems = @configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
|
|
196
|
+
roots = [Dir.pwd]
|
|
197
|
+
Array(@configuration.signature_paths).each { |sp| roots << File.expand_path(sp) }
|
|
198
|
+
trusted_gems.each do |gem_name|
|
|
199
|
+
path = trusted_gem_root(gem_name)
|
|
200
|
+
roots << path if path
|
|
201
|
+
end
|
|
202
|
+
@configuration.plugins_io_allowed_paths.each { |p| roots << File.expand_path(p) }
|
|
203
|
+
|
|
204
|
+
Plugin::TrustPolicy.new(
|
|
205
|
+
trusted_gems: trusted_gems,
|
|
206
|
+
allowed_read_roots: roots,
|
|
207
|
+
network_policy: @configuration.plugins_io_network,
|
|
208
|
+
allowed_url_hosts: @configuration.plugins_io_allowed_url_hosts
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def trusted_gem_name(entry)
|
|
213
|
+
case entry
|
|
214
|
+
when String then entry
|
|
215
|
+
when Hash then entry["gem"] || entry["id"]
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def trusted_gem_root(gem_name)
|
|
220
|
+
return nil if gem_name.nil? || gem_name.empty?
|
|
221
|
+
|
|
222
|
+
spec = Gem.loaded_specs[gem_name]
|
|
223
|
+
spec&.full_gem_path # rigor:disable undefined-method
|
|
224
|
+
rescue StandardError
|
|
225
|
+
nil
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def run_plugin_prepare
|
|
229
|
+
return [] if @plugin_registry.empty?
|
|
230
|
+
|
|
231
|
+
@plugin_registry.plugins.flat_map do |plugin|
|
|
232
|
+
plugin.prepare(plugin.services)
|
|
233
|
+
[]
|
|
234
|
+
rescue StandardError => e
|
|
235
|
+
[plugin_prepare_error_diagnostic(plugin, e)]
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def plugin_prepare_error_diagnostic(plugin, error)
|
|
240
|
+
plugin_id = safe_plugin_id(plugin)
|
|
241
|
+
Diagnostic.new(
|
|
242
|
+
path: ".rigor.yml",
|
|
243
|
+
line: 1,
|
|
244
|
+
column: 1,
|
|
245
|
+
message: "plugin #{plugin_id.inspect} raised during prepare: " \
|
|
246
|
+
"#{error.class}: #{error.message}",
|
|
247
|
+
severity: :error,
|
|
248
|
+
rule: "runtime-error",
|
|
249
|
+
source_family: :plugin_loader
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def plugin_emitted_diagnostics(path, root, scope)
|
|
254
|
+
return [] if @plugin_registry.empty?
|
|
255
|
+
|
|
256
|
+
@plugin_registry.plugins.flat_map do |plugin|
|
|
257
|
+
collect_plugin_diagnostics(plugin, path, root, scope)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def collect_plugin_diagnostics(plugin, path, root, scope)
|
|
262
|
+
raw = plugin.diagnostics_for_file(path: path, scope: scope, root: root)
|
|
263
|
+
Array(raw).map { |diagnostic| stamp_plugin_diagnostic(diagnostic, plugin.manifest.id) }
|
|
264
|
+
rescue StandardError => e
|
|
265
|
+
[plugin_runtime_error_diagnostic(path, plugin, e)]
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def stamp_plugin_diagnostic(diagnostic, plugin_id)
|
|
269
|
+
Diagnostic.new(
|
|
270
|
+
path: diagnostic.path,
|
|
271
|
+
line: diagnostic.line,
|
|
272
|
+
column: diagnostic.column,
|
|
273
|
+
message: diagnostic.message,
|
|
274
|
+
severity: diagnostic.severity,
|
|
275
|
+
rule: diagnostic.rule,
|
|
276
|
+
source_family: "plugin.#{plugin_id}"
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def plugin_runtime_error_diagnostic(path, plugin, error)
|
|
281
|
+
plugin_id = safe_plugin_id(plugin)
|
|
282
|
+
Diagnostic.new(
|
|
283
|
+
path: path,
|
|
284
|
+
line: 1,
|
|
285
|
+
column: 1,
|
|
286
|
+
message: "plugin #{plugin_id.inspect} raised during diagnostics_for_file: " \
|
|
287
|
+
"#{error.class}: #{error.message}",
|
|
288
|
+
severity: :error,
|
|
289
|
+
rule: "runtime-error",
|
|
290
|
+
source_family: :plugin_loader
|
|
291
|
+
)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def safe_plugin_id(plugin)
|
|
295
|
+
plugin.manifest.id
|
|
296
|
+
rescue StandardError
|
|
297
|
+
plugin.class.to_s
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def explain_diagnostics(path, root, scope)
|
|
301
|
+
return [] unless @explain
|
|
302
|
+
|
|
303
|
+
result = Inference::CoverageScanner.new(scope: scope).scan(root)
|
|
304
|
+
result.events.map { |event| explain_diagnostic(path, event) }
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def explain_diagnostic(path, event)
|
|
308
|
+
location = event.location
|
|
309
|
+
line = location ? location.start_line : 1
|
|
310
|
+
column = location ? location.start_column + 1 : 1
|
|
311
|
+
Diagnostic.new(
|
|
312
|
+
path: path,
|
|
313
|
+
line: line,
|
|
314
|
+
column: column,
|
|
315
|
+
message: "fail-soft fallback at #{event.node_class}: #{event.inner_type.describe(:short)}",
|
|
316
|
+
severity: :info,
|
|
317
|
+
rule: "fallback"
|
|
318
|
+
)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def parse_diagnostics(path, parse_result)
|
|
322
|
+
parse_result.errors.map do |error|
|
|
323
|
+
location = error.location
|
|
324
|
+
Diagnostic.new(
|
|
325
|
+
path: path,
|
|
326
|
+
line: location.start_line,
|
|
327
|
+
column: location.start_column + 1,
|
|
328
|
+
message: error.message,
|
|
329
|
+
severity: :error
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def analyzer_error(path, message)
|
|
335
|
+
Diagnostic.new(path: path, line: 1, column: 1, message: message, severity: :error)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../inference/hkt_registry"
|
|
4
|
+
require_relative "../inference/hkt_body"
|
|
5
|
+
require_relative "../inference/hkt_body_parser"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Builtins
|
|
9
|
+
# ADR-20 slices 2c + 3 — Rigor-bundled Lightweight HKT
|
|
10
|
+
# registrations that ship with every analyzer instance.
|
|
11
|
+
# The set is intentionally small at v0.1.x: only the URIs
|
|
12
|
+
# whose payoff justifies hardcoded definitions. Plugin
|
|
13
|
+
# authors register more URIs through their manifests; user
|
|
14
|
+
# `.rbs` overlays register through the
|
|
15
|
+
# `%a{rigor:v1:hkt_register}` /
|
|
16
|
+
# `%a{rigor:v1:hkt_define}` annotations Slice 1 ships.
|
|
17
|
+
#
|
|
18
|
+
# Today's contents:
|
|
19
|
+
#
|
|
20
|
+
# - `json::value[K]` — the recursive sum stdlib's
|
|
21
|
+
# `JSON.parse` returns. Body:
|
|
22
|
+
#
|
|
23
|
+
# nil | true | false | Integer | Float | String
|
|
24
|
+
# | Array[App[json::value, K]]
|
|
25
|
+
# | Hash[K, App[json::value, K]]
|
|
26
|
+
#
|
|
27
|
+
# The reducer handles the self-recursive `App` nodes via
|
|
28
|
+
# lazy "tying-the-knot" (see {HktReducer}). `K = String`
|
|
29
|
+
# matches stdlib's default key handling; `K = Symbol`
|
|
30
|
+
# matches `symbolize_names: true`.
|
|
31
|
+
module HktBuiltins
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# Built via the body-string parser (slice 2b/2c) so the
|
|
35
|
+
# bundled overlay exercises the same authoring surface
|
|
36
|
+
# third-party plugins use. The body matches what user
|
|
37
|
+
# `.rbs` overlays would write through a
|
|
38
|
+
# `%a{rigor:v1:hkt_define: ...body=...}` annotation.
|
|
39
|
+
JSON_VALUE_BODY = "nil | true | false | Integer | Float | String | " \
|
|
40
|
+
"Array[App[json::value, K]] | Hash[K, App[json::value, K]]"
|
|
41
|
+
private_constant :JSON_VALUE_BODY
|
|
42
|
+
|
|
43
|
+
def json_value_body_tree
|
|
44
|
+
Rigor::Inference::HktBodyParser.parse(JSON_VALUE_BODY, params: [:K])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# `csv::parsed[K]` — `Array[Array[K | nil]]` (CSV.parse's
|
|
48
|
+
# no-headers shape: an Array of rows; each row is an
|
|
49
|
+
# Array of optionally-nil cell values). When
|
|
50
|
+
# `headers: true` the runtime returns a `CSV::Table` /
|
|
51
|
+
# `CSV::Row` shape instead — that case is NOT covered
|
|
52
|
+
# by the bundled override (CSV::Row is its own class
|
|
53
|
+
# with Hash + Array access; a future slice may add a
|
|
54
|
+
# separate URI or a discriminator hook for it).
|
|
55
|
+
CSV_PARSED_BODY = "Array[Array[K | nil]]"
|
|
56
|
+
private_constant :CSV_PARSED_BODY
|
|
57
|
+
|
|
58
|
+
def csv_parsed_body_tree
|
|
59
|
+
Rigor::Inference::HktBodyParser.parse(CSV_PARSED_BODY, params: [:K])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def json_value_registration
|
|
63
|
+
Rigor::Inference::HktRegistry::Registration.new(
|
|
64
|
+
uri: :"json::value",
|
|
65
|
+
arity: 1,
|
|
66
|
+
variance: [:out],
|
|
67
|
+
bound: Rigor::Type::Combinator.untyped
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def json_value_definition
|
|
72
|
+
Rigor::Inference::HktRegistry.definition_with_body_tree(
|
|
73
|
+
uri: :"json::value",
|
|
74
|
+
params: [:K],
|
|
75
|
+
body_tree: json_value_body_tree,
|
|
76
|
+
source_path: __FILE__,
|
|
77
|
+
source_line: __LINE__ - 5
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def csv_parsed_registration
|
|
82
|
+
Rigor::Inference::HktRegistry::Registration.new(
|
|
83
|
+
uri: :"csv::parsed",
|
|
84
|
+
arity: 1,
|
|
85
|
+
variance: [:out],
|
|
86
|
+
bound: Rigor::Type::Combinator.untyped
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def csv_parsed_definition
|
|
91
|
+
Rigor::Inference::HktRegistry.definition_with_body_tree(
|
|
92
|
+
uri: :"csv::parsed",
|
|
93
|
+
params: [:K],
|
|
94
|
+
body_tree: csv_parsed_body_tree,
|
|
95
|
+
source_path: __FILE__,
|
|
96
|
+
source_line: __LINE__ - 5
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @return [Rigor::Inference::HktRegistry] frozen registry
|
|
101
|
+
# pre-seeded with all bundled HKT registrations +
|
|
102
|
+
# bodies. Allocated fresh each call rather than
|
|
103
|
+
# memoised — memoisation through a module-level
|
|
104
|
+
# `@registry` ivar surfaces a `Ractor::IsolationError`
|
|
105
|
+
# in pool workers (the ivar's contents include
|
|
106
|
+
# `HktBody::AppRef` Symbol-keyed structures that the
|
|
107
|
+
# current Ractor shareability audit hasn't yet been
|
|
108
|
+
# walked through). The registry is small enough that
|
|
109
|
+
# per-Environment construction is acceptable; an
|
|
110
|
+
# eager-frozen constant is a future optimisation
|
|
111
|
+
# once ADR-15 phase 4b.x covers the dependency graph.
|
|
112
|
+
def registry
|
|
113
|
+
Rigor::Inference::HktRegistry.new(
|
|
114
|
+
registrations: [json_value_registration, csv_parsed_registration],
|
|
115
|
+
definitions: [json_value_definition, csv_parsed_definition]
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# ADR-20 slice 3 — hardcoded `(class_name, method_name,
|
|
120
|
+
# kind) => HKT application` table consulted by the
|
|
121
|
+
# dispatcher's new HKT-builtin tier. Sits ABOVE
|
|
122
|
+
# `RbsDispatch.try_dispatch` so a known stdlib method
|
|
123
|
+
# (`JSON.parse`, `JSON.parse!`) gets the reduced HKT
|
|
124
|
+
# type instead of the upstream rbs gem's `untyped`
|
|
125
|
+
# return. The annotation-based `%a{rigor:v1:return:
|
|
126
|
+
# App[...]}` path (parsed by
|
|
127
|
+
# `RbsExtended.parse_return_type_override`) is the
|
|
128
|
+
# general extension surface for user-authored sigs;
|
|
129
|
+
# this table is the Rigor-bundled shortcut for the
|
|
130
|
+
# handful of stdlib methods whose RBS declarations
|
|
131
|
+
# cannot be cleanly overridden via RBS overlay merging.
|
|
132
|
+
#
|
|
133
|
+
# Each entry maps to a hash with `:uri` and `:args`
|
|
134
|
+
# (an array of Ruby class names). The dispatcher
|
|
135
|
+
# builds `Type::App.new(uri, args.map { Nominal })`,
|
|
136
|
+
# then reduces via the env's `hkt_registry` so the
|
|
137
|
+
# caller observes the unfolded form
|
|
138
|
+
# (`Union[nil, true, false, ..., Array[App[json::value,
|
|
139
|
+
# String]], Hash[String, App[json::value, String]]]`)
|
|
140
|
+
# rather than the opaque carrier.
|
|
141
|
+
JSON_VALUE_SPEC = {
|
|
142
|
+
uri: :"json::value",
|
|
143
|
+
args: ["String"],
|
|
144
|
+
discriminator: :json_symbolize_names,
|
|
145
|
+
post_reduce: nil
|
|
146
|
+
}.freeze
|
|
147
|
+
private_constant :JSON_VALUE_SPEC
|
|
148
|
+
|
|
149
|
+
# YAML / Psych.safe_load reuse the json::value reducer
|
|
150
|
+
# for the JSON-equivalent leaf set BUT additionally
|
|
151
|
+
# honour `permitted_classes: [<Class>, ...]` literal
|
|
152
|
+
# Array arguments, unioning each permitted class as an
|
|
153
|
+
# extra arm of the result. Slice 2c-bis behaviour.
|
|
154
|
+
YAML_SAFE_VALUE_SPEC = {
|
|
155
|
+
uri: :"json::value",
|
|
156
|
+
args: ["String"],
|
|
157
|
+
discriminator: :json_symbolize_names,
|
|
158
|
+
post_reduce: :yaml_permitted_classes
|
|
159
|
+
}.freeze
|
|
160
|
+
private_constant :YAML_SAFE_VALUE_SPEC
|
|
161
|
+
|
|
162
|
+
CSV_PARSED_SPEC = {
|
|
163
|
+
uri: :"csv::parsed",
|
|
164
|
+
args: ["String"],
|
|
165
|
+
discriminator: nil,
|
|
166
|
+
post_reduce: nil
|
|
167
|
+
}.freeze
|
|
168
|
+
private_constant :CSV_PARSED_SPEC
|
|
169
|
+
|
|
170
|
+
METHOD_RETURN_OVERRIDES = {
|
|
171
|
+
# JSON — stdlib's `json` library. Upstream rbs declares
|
|
172
|
+
# `(string, ?options) -> untyped`; the HKT-builtin tier
|
|
173
|
+
# tightens to the recursive `json::value[K]` union.
|
|
174
|
+
# `load_file` / `load_file!` share the `?options` slot
|
|
175
|
+
# so the `symbolize_names: true` discriminator applies
|
|
176
|
+
# to them too (just like `parse` / `load`).
|
|
177
|
+
["JSON", :parse, :singleton] => JSON_VALUE_SPEC,
|
|
178
|
+
["JSON", :parse!, :singleton] => JSON_VALUE_SPEC,
|
|
179
|
+
["JSON", :load, :singleton] => JSON_VALUE_SPEC,
|
|
180
|
+
["JSON", :load_file, :singleton] => JSON_VALUE_SPEC,
|
|
181
|
+
["JSON", :load_file!, :singleton] => JSON_VALUE_SPEC,
|
|
182
|
+
# YAML.safe_load / Psych.safe_load — default
|
|
183
|
+
# `permitted_classes: []` admits exactly the JSON
|
|
184
|
+
# vocabulary (nil / true / false / Integer / Float /
|
|
185
|
+
# String / Array / Hash), so the json::value tree
|
|
186
|
+
# also describes them. When the call passes a literal
|
|
187
|
+
# `permitted_classes: [Date, Symbol, ...]` Array, the
|
|
188
|
+
# `:yaml_permitted_classes` post_reduce unions each
|
|
189
|
+
# named class into the result. Non-literal options
|
|
190
|
+
# (a variable, a constant reference, a `+ classes`
|
|
191
|
+
# concat) silently no-op and the caller observes the
|
|
192
|
+
# base json::value envelope only. YAML.load /
|
|
193
|
+
# YAML.unsafe_load deliberately stay out of the
|
|
194
|
+
# override table — they can return ANY Ruby object
|
|
195
|
+
# and have no useful HKT envelope.
|
|
196
|
+
["YAML", :safe_load, :singleton] => YAML_SAFE_VALUE_SPEC,
|
|
197
|
+
["YAML", :safe_load_file, :singleton] => YAML_SAFE_VALUE_SPEC,
|
|
198
|
+
["Psych", :safe_load, :singleton] => YAML_SAFE_VALUE_SPEC,
|
|
199
|
+
["Psych", :safe_load_file, :singleton] => YAML_SAFE_VALUE_SPEC,
|
|
200
|
+
# CSV.parse / CSV.read — no-headers shape only.
|
|
201
|
+
# Upstream rbs declares broader return shapes but
|
|
202
|
+
# the common case is `Array[Array[String?]]` which
|
|
203
|
+
# the `csv::parsed[String]` URI matches. The
|
|
204
|
+
# `headers: true` shape (`CSV::Table` of `CSV::Row`)
|
|
205
|
+
# is NOT covered — calls passing the option fall
|
|
206
|
+
# through to the upstream RBS type. CSV.foreach also
|
|
207
|
+
# falls through (it yields rows rather than
|
|
208
|
+
# returning a typed structure).
|
|
209
|
+
["CSV", :parse, :singleton] => CSV_PARSED_SPEC,
|
|
210
|
+
["CSV", :read, :singleton] => CSV_PARSED_SPEC
|
|
211
|
+
}.freeze
|
|
212
|
+
|
|
213
|
+
# @return [Rigor::Type, nil] the reduced HKT type for
|
|
214
|
+
# the given (class_name, method_name, kind) triple,
|
|
215
|
+
# or `nil` when no built-in override is registered.
|
|
216
|
+
# When `arg_types` is supplied AND the entry carries a
|
|
217
|
+
# `:discriminator` symbol, the discriminator may swap
|
|
218
|
+
# the spec's default args for an alternate (e.g.
|
|
219
|
+
# `JSON.parse(str, symbolize_names: true)` discriminates
|
|
220
|
+
# `K = Symbol` instead of the default `K = String`).
|
|
221
|
+
def method_return_override(class_name:, method_name:, kind:, arg_types: nil, hkt_registry: nil)
|
|
222
|
+
spec = METHOD_RETURN_OVERRIDES[[class_name, method_name.to_sym, kind]]
|
|
223
|
+
return nil unless spec
|
|
224
|
+
|
|
225
|
+
args = discriminated_args(spec, arg_types)
|
|
226
|
+
registration = hkt_registry&.registration(spec[:uri])
|
|
227
|
+
bound = registration&.bound || Rigor::Type::Combinator.untyped
|
|
228
|
+
app = Rigor::Type::App.new(spec[:uri], args, bound: bound)
|
|
229
|
+
|
|
230
|
+
reduced =
|
|
231
|
+
if hkt_registry.nil? || !hkt_registry.defined?(spec[:uri])
|
|
232
|
+
app
|
|
233
|
+
else
|
|
234
|
+
hkt_registry.reduce(app) || app
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
apply_post_reduce(spec[:post_reduce], reduced, arg_types)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Per-spec discriminator dispatch. Slice 3 ships one
|
|
241
|
+
# built-in discriminator (`json_symbolize_names`) that
|
|
242
|
+
# observes the optional 2nd argument's `HashShape` for a
|
|
243
|
+
# literal `symbolize_names: true` entry. Plugin / Rigor-
|
|
244
|
+
# bundled callers wanting their own discriminators add a
|
|
245
|
+
# branch here.
|
|
246
|
+
def discriminated_args(spec, arg_types)
|
|
247
|
+
default_args = spec[:args].map { |n| Rigor::Type::Nominal.new(n) }
|
|
248
|
+
return default_args if arg_types.nil?
|
|
249
|
+
return default_args unless spec[:discriminator] == :json_symbolize_names
|
|
250
|
+
return default_args unless json_symbolize_names?(arg_types)
|
|
251
|
+
|
|
252
|
+
[Rigor::Type::Nominal.new("Symbol")]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Returns true iff the call-site's 2nd argument is a
|
|
256
|
+
# `Type::HashShape` carrying a literal
|
|
257
|
+
# `symbolize_names: true` entry. Anything else
|
|
258
|
+
# (no second arg, non-HashShape, missing key, non-literal
|
|
259
|
+
# `true`) returns false so the default `K = String`
|
|
260
|
+
# branch wins.
|
|
261
|
+
def json_symbolize_names?(arg_types)
|
|
262
|
+
return false unless arg_types.is_a?(Array) && arg_types.size >= 2
|
|
263
|
+
|
|
264
|
+
opts = arg_types[1]
|
|
265
|
+
return false unless opts.is_a?(Rigor::Type::HashShape)
|
|
266
|
+
|
|
267
|
+
value = opts.pairs[:symbolize_names] || opts.pairs["symbolize_names"]
|
|
268
|
+
value.is_a?(Rigor::Type::Constant) && value.value == true
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Slice 2c-bis — post-reduce hook. Receives the already-
|
|
272
|
+
# reduced `Type` and the call-site's `arg_types`; returns
|
|
273
|
+
# a (possibly augmented) `Type`. `kind = nil` is the
|
|
274
|
+
# identity (passes the reduced type through unchanged).
|
|
275
|
+
# Only `:yaml_permitted_classes` is implemented today;
|
|
276
|
+
# plugin / Rigor-bundled callers wanting their own
|
|
277
|
+
# post-reduce hooks add a branch here.
|
|
278
|
+
def apply_post_reduce(kind, reduced, arg_types)
|
|
279
|
+
case kind
|
|
280
|
+
when :yaml_permitted_classes
|
|
281
|
+
augment_with_yaml_permitted_classes(reduced, arg_types)
|
|
282
|
+
else
|
|
283
|
+
# `nil` (no post-reduce declared) and any future
|
|
284
|
+
# unrecognised kind both pass the reduced type
|
|
285
|
+
# through unchanged. Unknown kinds are silently
|
|
286
|
+
# tolerated rather than raised because adding a
|
|
287
|
+
# new kind on a Rigor upgrade should not crash a
|
|
288
|
+
# stale METHOD_RETURN_OVERRIDES entry on the
|
|
289
|
+
# caller side.
|
|
290
|
+
reduced
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Inspects arg_types for a `permitted_classes: [<Class>,
|
|
295
|
+
# ...]` literal Array in the options Hash and unions
|
|
296
|
+
# each named class into the reduced result. Non-literal
|
|
297
|
+
# `permitted_classes:` values (a variable, a constant
|
|
298
|
+
# reference, a concat) silently no-op and the caller
|
|
299
|
+
# observes the base json::value envelope only. Defensive
|
|
300
|
+
# against the various ways Ruby literal arrays surface
|
|
301
|
+
# as Rigor types: `Tuple[Singleton<Date>]` for a single
|
|
302
|
+
# element, `Tuple[Singleton<Date>, Singleton<Symbol>]`
|
|
303
|
+
# for multiple, `Nominal[Array, [Singleton<...>]]` if
|
|
304
|
+
# the analyzer widened (rare for literal arrays).
|
|
305
|
+
def augment_with_yaml_permitted_classes(reduced, arg_types)
|
|
306
|
+
return reduced unless arg_types.is_a?(Array) && arg_types.size >= 2
|
|
307
|
+
|
|
308
|
+
opts = arg_types[1]
|
|
309
|
+
return reduced unless opts.is_a?(Rigor::Type::HashShape)
|
|
310
|
+
|
|
311
|
+
value = opts.pairs[:permitted_classes] || opts.pairs["permitted_classes"]
|
|
312
|
+
return reduced if value.nil?
|
|
313
|
+
|
|
314
|
+
extras = permitted_class_nominals(value)
|
|
315
|
+
return reduced if extras.empty?
|
|
316
|
+
|
|
317
|
+
Rigor::Type::Combinator.union(reduced, *extras)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Extract Singleton-class elements from a Tuple or
|
|
321
|
+
# Array-shape carrier, mapping each to its Nominal
|
|
322
|
+
# counterpart. Returns an empty array when no static
|
|
323
|
+
# Singletons are reachable (e.g. value is `Dynamic[T]`,
|
|
324
|
+
# element types are non-Singleton, etc.).
|
|
325
|
+
def permitted_class_nominals(value)
|
|
326
|
+
candidates =
|
|
327
|
+
if value.is_a?(Rigor::Type::Tuple)
|
|
328
|
+
value.elements
|
|
329
|
+
elsif value.is_a?(Rigor::Type::Nominal) && value.class_name == "Array" && value.type_args.size == 1
|
|
330
|
+
element = value.type_args.first
|
|
331
|
+
element.is_a?(Rigor::Type::Union) ? element.members : [element]
|
|
332
|
+
else
|
|
333
|
+
[]
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
candidates.filter_map do |c|
|
|
337
|
+
c.is_a?(Rigor::Type::Singleton) ? Rigor::Type::Nominal.new(c.class_name) : nil
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
@@ -419,9 +419,13 @@ module Rigor
|
|
|
419
419
|
elsif (literal = @scanner.scan(SIGNED_INT))
|
|
420
420
|
TypeNode::IntegerLiteral.new(value: Integer(literal))
|
|
421
421
|
elsif @scanner.scan(SYMBOL_LITERAL)
|
|
422
|
-
|
|
422
|
+
# StringScanner#[] accepts Symbol for named captures
|
|
423
|
+
# (Ruby behaviour); upstream RBS shim only declares the
|
|
424
|
+
# positional-capture (Integer) overload, so the
|
|
425
|
+
# argument-type-mismatch diagnostic is suppressed.
|
|
426
|
+
TypeNode::SymbolLiteral.new(value: @scanner[:value].to_sym) # rigor:disable argument-type-mismatch
|
|
423
427
|
elsif @scanner.scan(STRING_LITERAL)
|
|
424
|
-
TypeNode::StringLiteral.new(value: @scanner[:value])
|
|
428
|
+
TypeNode::StringLiteral.new(value: @scanner[:value]) # rigor:disable argument-type-mismatch
|
|
425
429
|
else
|
|
426
430
|
parse_type_ast
|
|
427
431
|
end
|