rigortype 0.0.1
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 +7 -0
- data/LICENSE +373 -0
- data/README.md +152 -0
- data/exe/rigor +9 -0
- data/lib/rigor/analysis/check_rules.rb +503 -0
- data/lib/rigor/analysis/diagnostic.rb +35 -0
- data/lib/rigor/analysis/fact_store.rb +133 -0
- data/lib/rigor/analysis/result.rb +29 -0
- data/lib/rigor/analysis/runner.rb +119 -0
- data/lib/rigor/ast/type_node.rb +41 -0
- data/lib/rigor/ast.rb +22 -0
- data/lib/rigor/cli/type_of_command.rb +160 -0
- data/lib/rigor/cli/type_of_renderer.rb +88 -0
- data/lib/rigor/cli/type_scan_command.rb +160 -0
- data/lib/rigor/cli/type_scan_renderer.rb +165 -0
- data/lib/rigor/cli/type_scan_report.rb +32 -0
- data/lib/rigor/cli.rb +195 -0
- data/lib/rigor/configuration.rb +49 -0
- data/lib/rigor/environment/class_registry.rb +141 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +64 -0
- data/lib/rigor/environment/rbs_loader.rb +244 -0
- data/lib/rigor/environment.rb +177 -0
- data/lib/rigor/inference/acceptance.rb +444 -0
- data/lib/rigor/inference/block_parameter_binder.rb +198 -0
- data/lib/rigor/inference/closure_escape_analyzer.rb +191 -0
- data/lib/rigor/inference/coverage_scanner.rb +85 -0
- data/lib/rigor/inference/expression_typer.rb +831 -0
- data/lib/rigor/inference/fallback.rb +35 -0
- data/lib/rigor/inference/fallback_tracer.rb +64 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +102 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +169 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +421 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +336 -0
- data/lib/rigor/inference/method_dispatcher.rb +213 -0
- data/lib/rigor/inference/method_parameter_binder.rb +257 -0
- data/lib/rigor/inference/multi_target_binder.rb +143 -0
- data/lib/rigor/inference/narrowing.rb +1008 -0
- data/lib/rigor/inference/rbs_type_translator.rb +219 -0
- data/lib/rigor/inference/scope_indexer.rb +468 -0
- data/lib/rigor/inference/statement_evaluator.rb +1017 -0
- data/lib/rigor/rbs_extended.rb +98 -0
- data/lib/rigor/scope.rb +340 -0
- data/lib/rigor/source/node_locator.rb +104 -0
- data/lib/rigor/source/node_walker.rb +37 -0
- data/lib/rigor/source.rb +15 -0
- data/lib/rigor/testing.rb +65 -0
- data/lib/rigor/trinary.rb +108 -0
- data/lib/rigor/type/accepts_result.rb +109 -0
- data/lib/rigor/type/bot.rb +57 -0
- data/lib/rigor/type/combinator.rb +148 -0
- data/lib/rigor/type/constant.rb +90 -0
- data/lib/rigor/type/dynamic.rb +60 -0
- data/lib/rigor/type/hash_shape.rb +246 -0
- data/lib/rigor/type/nominal.rb +83 -0
- data/lib/rigor/type/singleton.rb +65 -0
- data/lib/rigor/type/top.rb +56 -0
- data/lib/rigor/type/tuple.rb +84 -0
- data/lib/rigor/type/union.rb +65 -0
- data/lib/rigor/type.rb +23 -0
- data/lib/rigor/version.rb +5 -0
- data/lib/rigor.rb +29 -0
- data/sig/rigor/analysis/fact_store.rbs +51 -0
- data/sig/rigor/ast.rbs +11 -0
- data/sig/rigor/environment.rbs +59 -0
- data/sig/rigor/inference.rbs +151 -0
- data/sig/rigor/rbs_extended.rbs +22 -0
- data/sig/rigor/scope.rbs +49 -0
- data/sig/rigor/source.rbs +20 -0
- data/sig/rigor/testing.rbs +9 -0
- data/sig/rigor/trinary.rbs +29 -0
- data/sig/rigor/type.rbs +171 -0
- data/sig/rigor.rbs +70 -0
- metadata +260 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
|
|
5
|
+
require_relative "../type"
|
|
6
|
+
require_relative "../inference/rbs_type_translator"
|
|
7
|
+
require_relative "rbs_hierarchy"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
class Environment
|
|
11
|
+
# Loads RBS class declarations and method definitions from disk and
|
|
12
|
+
# exposes them to the inference engine in a small, stable surface.
|
|
13
|
+
#
|
|
14
|
+
# Slice 4 phase 1 only enabled the RBS *core* signatures shipped with
|
|
15
|
+
# the `rbs` gem (`Object`, `Integer`, `String`, `Array`, ...). Phase
|
|
16
|
+
# 2a adds opt-in stdlib library loading (`pathname`, `json`,
|
|
17
|
+
# `tempfile`, ...) and arbitrary-directory signature loading
|
|
18
|
+
# (typically the project's local `sig/` tree). Both are off by
|
|
19
|
+
# default on `RbsLoader.default` so the core-only fast path stays
|
|
20
|
+
# cheap; project-aware loading is opted into through
|
|
21
|
+
# {Environment.for_project} or by constructing a custom loader.
|
|
22
|
+
#
|
|
23
|
+
# The default instance is shared across the process: building the
|
|
24
|
+
# core RBS environment costs hundreds of milliseconds and the data
|
|
25
|
+
# is read-only. The shared instance is frozen, but holds a mutable
|
|
26
|
+
# state hash for lazy memoization of the heavy `RBS::Environment`
|
|
27
|
+
# and `RBS::DefinitionBuilder` -- the user-visible API stays purely
|
|
28
|
+
# functional.
|
|
29
|
+
#
|
|
30
|
+
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
31
|
+
# rubocop:disable Metrics/ClassLength
|
|
32
|
+
class RbsLoader
|
|
33
|
+
class << self
|
|
34
|
+
def default
|
|
35
|
+
@default ||= new.freeze
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Used by tests to discard the cached default loader; production
|
|
39
|
+
# code MUST NOT call this. The shared loader holds a several-MB
|
|
40
|
+
# RBS::Environment, so dropping it during a normal run wastes the
|
|
41
|
+
# cost of rebuilding it.
|
|
42
|
+
def reset_default!
|
|
43
|
+
@default = nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :libraries, :signature_paths
|
|
48
|
+
|
|
49
|
+
# @param libraries [Array<String, Symbol>] stdlib library names to
|
|
50
|
+
# load on top of core (e.g., `["pathname", "json"]`). Empty by
|
|
51
|
+
# default. Each entry MUST correspond to a directory under the
|
|
52
|
+
# `rbs` gem's `stdlib/` tree; unknown names are silently dropped
|
|
53
|
+
# on environment build (the underlying `RBS::EnvironmentLoader`
|
|
54
|
+
# raises and we fail-soft).
|
|
55
|
+
# @param signature_paths [Array<String, Pathname>] additional
|
|
56
|
+
# directories of `.rbs` files to load (typically the project's
|
|
57
|
+
# `sig/` tree). Non-existent or non-directory paths are filtered
|
|
58
|
+
# out at build time so the loader stays robust to fixtures and
|
|
59
|
+
# bare repositories.
|
|
60
|
+
def initialize(libraries: [], signature_paths: [])
|
|
61
|
+
@libraries = libraries.map(&:to_s).freeze
|
|
62
|
+
@signature_paths = signature_paths.map { |p| Pathname(p) }.freeze
|
|
63
|
+
@state = { env: nil, builder: nil }
|
|
64
|
+
@instance_definition_cache = {}
|
|
65
|
+
@singleton_definition_cache = {}
|
|
66
|
+
@class_known_cache = {}
|
|
67
|
+
@hierarchy = RbsHierarchy.new(self)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns true when an RBS class or module declaration with the given
|
|
71
|
+
# name is loaded. Accepts unprefixed or top-level-prefixed names
|
|
72
|
+
# ("Integer" or "::Integer"). Memoized per-name (positive and
|
|
73
|
+
# negative results both cache).
|
|
74
|
+
def class_known?(name)
|
|
75
|
+
key = name.to_s
|
|
76
|
+
return @class_known_cache[key] if @class_known_cache.key?(key)
|
|
77
|
+
|
|
78
|
+
@class_known_cache[key] = compute_class_known(name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @return [RBS::Definition, nil] the resolved instance definition
|
|
82
|
+
# for `class_name`, or nil when the class is unknown or its
|
|
83
|
+
# definition cannot be built (RBS may raise on broken hierarchies;
|
|
84
|
+
# we fail-soft and return nil so the caller can fall back).
|
|
85
|
+
def instance_definition(class_name)
|
|
86
|
+
key = class_name.to_s
|
|
87
|
+
return @instance_definition_cache[key] if @instance_definition_cache.key?(key)
|
|
88
|
+
|
|
89
|
+
@instance_definition_cache[key] = build_instance_definition(class_name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [RBS::Definition::Method, nil]
|
|
93
|
+
def instance_method(class_name:, method_name:)
|
|
94
|
+
definition = instance_definition(class_name)
|
|
95
|
+
return nil unless definition
|
|
96
|
+
|
|
97
|
+
definition.methods[method_name.to_sym]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @return [RBS::Definition, nil] the resolved singleton (class
|
|
101
|
+
# object) definition for `class_name`. The methods on this
|
|
102
|
+
# definition are the *class methods* of `class_name`, including
|
|
103
|
+
# those inherited from `Class` and `Module` for class types.
|
|
104
|
+
# Returns nil for unknown names and on RBS build errors (fail-soft).
|
|
105
|
+
def singleton_definition(class_name)
|
|
106
|
+
key = class_name.to_s
|
|
107
|
+
return @singleton_definition_cache[key] if @singleton_definition_cache.key?(key)
|
|
108
|
+
|
|
109
|
+
@singleton_definition_cache[key] = build_singleton_definition(class_name)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @return [RBS::Definition::Method, nil] the class method on
|
|
113
|
+
# `class_name`. For example, `singleton_method(class_name:
|
|
114
|
+
# "Integer", method_name: :sqrt)` returns the definition for
|
|
115
|
+
# `Integer.sqrt`, while `singleton_method(class_name: "Foo",
|
|
116
|
+
# method_name: :new)` returns Class#new for any class type.
|
|
117
|
+
def singleton_method(class_name:, method_name:)
|
|
118
|
+
definition = singleton_definition(class_name)
|
|
119
|
+
return nil unless definition
|
|
120
|
+
|
|
121
|
+
definition.methods[method_name.to_sym]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Slice 4 phase 2d. Returns the class's declared type-parameter
|
|
125
|
+
# names as Symbols (e.g., `[:Elem]` for `Array`, `[:K, :V]` for
|
|
126
|
+
# `Hash`). Used by the dispatcher to build the substitution map
|
|
127
|
+
# from receiver `type_args` into the method's return type. The
|
|
128
|
+
# instance definition is the canonical source because singleton
|
|
129
|
+
# methods (e.g., `Array.new`) parameterize over the same `Elem`
|
|
130
|
+
# as instance methods.
|
|
131
|
+
#
|
|
132
|
+
# Returns an empty array for non-generic classes and for unknown
|
|
133
|
+
# names (the loader stays fail-soft). NOTE: in the `rbs` gem,
|
|
134
|
+
# `RBS::Definition#type_params` returns `Array<Symbol>` directly,
|
|
135
|
+
# not the AST `TypeParam` object (those live on the AST level).
|
|
136
|
+
def class_type_param_names(class_name)
|
|
137
|
+
definition = instance_definition(class_name)
|
|
138
|
+
return [] unless definition
|
|
139
|
+
|
|
140
|
+
definition.type_params.dup
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def class_ordering(lhs, rhs)
|
|
144
|
+
@hierarchy.class_ordering(lhs, rhs)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Slice A constant-value lookup. Returns the translated
|
|
148
|
+
# `Rigor::Type` for a non-class constant declaration
|
|
149
|
+
# (`BUCKETS: Array[Symbol]`, `DEFAULT_PATH: String`, ...) or
|
|
150
|
+
# `nil` when no constant entry exists for `name` in the
|
|
151
|
+
# loaded RBS environment. Callers MUST treat the return
|
|
152
|
+
# value as authoritative when present and as "unknown" when
|
|
153
|
+
# nil; the loader does NOT consult the class declarations
|
|
154
|
+
# here — class objects are still resolved through
|
|
155
|
+
# {#class_known?} and `Environment#singleton_for_name`.
|
|
156
|
+
def constant_type(name)
|
|
157
|
+
rbs_name = parse_type_name(name)
|
|
158
|
+
return nil unless rbs_name
|
|
159
|
+
|
|
160
|
+
entry = env.constant_decls[rbs_name]
|
|
161
|
+
return nil unless entry
|
|
162
|
+
|
|
163
|
+
translated = Inference::RbsTypeTranslator.translate(entry.decl.type)
|
|
164
|
+
translated unless translated.is_a?(Type::Bot)
|
|
165
|
+
rescue StandardError
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private
|
|
170
|
+
|
|
171
|
+
def env
|
|
172
|
+
@state[:env] ||= build_env
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def builder
|
|
176
|
+
@state[:builder] ||= RBS::DefinitionBuilder.new(env: env)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def build_env
|
|
180
|
+
rbs_loader = RBS::EnvironmentLoader.new
|
|
181
|
+
@libraries.each do |library|
|
|
182
|
+
# Phase 2a deliberately fails-soft on unknown stdlib libraries
|
|
183
|
+
# so a stale `.rigor.yml` (or future config plumbing) does not
|
|
184
|
+
# take down the whole analyzer. Phase 2b will surface this
|
|
185
|
+
# through diagnostics once the configuration layer can name
|
|
186
|
+
# the offending source. The unknown-library check happens at
|
|
187
|
+
# `from_loader` time, not at `add` time, so we have to gate
|
|
188
|
+
# ahead of `add`.
|
|
189
|
+
next unless rbs_loader.has_library?(library: library, version: nil)
|
|
190
|
+
|
|
191
|
+
rbs_loader.add(library: library, version: nil)
|
|
192
|
+
end
|
|
193
|
+
@signature_paths.each do |path|
|
|
194
|
+
rbs_loader.add(path: path) if path.directory?
|
|
195
|
+
end
|
|
196
|
+
RBS::Environment.from_loader(rbs_loader).resolve_type_names
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def build_instance_definition(class_name)
|
|
200
|
+
rbs_name = parse_type_name(class_name)
|
|
201
|
+
return nil unless rbs_name
|
|
202
|
+
return nil unless env.class_decls.key?(rbs_name)
|
|
203
|
+
|
|
204
|
+
builder.build_instance(rbs_name)
|
|
205
|
+
rescue StandardError
|
|
206
|
+
nil
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def build_singleton_definition(class_name)
|
|
210
|
+
rbs_name = parse_type_name(class_name)
|
|
211
|
+
return nil unless rbs_name
|
|
212
|
+
return nil unless env.class_decls.key?(rbs_name)
|
|
213
|
+
|
|
214
|
+
builder.build_singleton(rbs_name)
|
|
215
|
+
rescue StandardError
|
|
216
|
+
nil
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def parse_type_name(name)
|
|
220
|
+
s = name.to_s
|
|
221
|
+
return nil if s.empty?
|
|
222
|
+
|
|
223
|
+
s = "::#{s}" unless s.start_with?("::")
|
|
224
|
+
RBS::TypeName.parse(s)
|
|
225
|
+
rescue StandardError
|
|
226
|
+
nil
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def compute_class_known(name)
|
|
230
|
+
rbs_name = parse_type_name(name)
|
|
231
|
+
return false unless rbs_name
|
|
232
|
+
|
|
233
|
+
# `RBS::Environment#class_decls` after `resolve_type_names`
|
|
234
|
+
# holds entries for both classes AND modules; the gem unifies
|
|
235
|
+
# them under one map post-resolution. Aliases live in their
|
|
236
|
+
# own table.
|
|
237
|
+
env.class_decls.key?(rbs_name) || env.class_alias_decls.key?(rbs_name)
|
|
238
|
+
rescue StandardError
|
|
239
|
+
false
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
# rubocop:enable Metrics/ClassLength
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "environment/class_registry"
|
|
4
|
+
require_relative "environment/rbs_loader"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
# The engine's view of the type universe outside the current scope.
|
|
8
|
+
# Slice 1 only exposed the class registry; Slice 4 adds the RBS loader,
|
|
9
|
+
# which threads through ExpressionTyper and MethodDispatcher to type
|
|
10
|
+
# constant references and method calls that the literal-typer and
|
|
11
|
+
# constant-folding tiers cannot answer.
|
|
12
|
+
#
|
|
13
|
+
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
14
|
+
class Environment
|
|
15
|
+
DEFAULT_PROJECT_SIG_DIR = "sig"
|
|
16
|
+
private_constant :DEFAULT_PROJECT_SIG_DIR
|
|
17
|
+
|
|
18
|
+
# Slice A stdlib expansion. Stdlib libraries that
|
|
19
|
+
# `Environment.for_project` loads on top of RBS core unless
|
|
20
|
+
# the caller passes an explicit `libraries:` array. Each
|
|
21
|
+
# entry MUST be a stdlib library name accepted by
|
|
22
|
+
# `RBS::EnvironmentLoader#has_library?`; unknown libraries
|
|
23
|
+
# MUST fail-soft (`RbsLoader#build_env` already filters
|
|
24
|
+
# through `has_library?`). The default set covers the common
|
|
25
|
+
# stdlib surface a Ruby program is likely to import
|
|
26
|
+
# (`pathname`, `optparse`, `json`, `yaml`, `fileutils`,
|
|
27
|
+
# `tempfile`, `uri`, `logger`, `date`) plus the analyzer-
|
|
28
|
+
# adjacent gems shipping their own RBS in this bundle
|
|
29
|
+
# (`prism`, `rbs`). On hosts where one of these libraries is
|
|
30
|
+
# not installed, the loader silently drops it.
|
|
31
|
+
#
|
|
32
|
+
# Callers MAY add to the default by passing
|
|
33
|
+
# `libraries: %w[csv ...]`; the explicit list is appended to
|
|
34
|
+
# `DEFAULT_LIBRARIES` and de-duplicated. Callers that need
|
|
35
|
+
# a strictly RBS-core view MUST construct an `RbsLoader`
|
|
36
|
+
# directly instead of going through `for_project`.
|
|
37
|
+
DEFAULT_LIBRARIES = %w[
|
|
38
|
+
pathname optparse json yaml fileutils tempfile uri logger date
|
|
39
|
+
prism rbs
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
attr_reader :class_registry, :rbs_loader
|
|
43
|
+
|
|
44
|
+
# @param class_registry [Rigor::Environment::ClassRegistry]
|
|
45
|
+
# @param rbs_loader [Rigor::Environment::RbsLoader, nil] when nil the
|
|
46
|
+
# environment is "RBS-blind"; useful in tests that want to assert
|
|
47
|
+
# how the engine behaves without RBS data. The default Environment
|
|
48
|
+
# wires the shared core loader, which is itself lazy: requesting an
|
|
49
|
+
# environment instance does NOT load RBS until a method or class
|
|
50
|
+
# query actually consults the loader.
|
|
51
|
+
def initialize(class_registry: ClassRegistry.default, rbs_loader: nil)
|
|
52
|
+
@class_registry = class_registry
|
|
53
|
+
@rbs_loader = rbs_loader
|
|
54
|
+
freeze
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
def default
|
|
59
|
+
@default ||= new(rbs_loader: RbsLoader.default).freeze
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Builds an Environment that consults the project's local
|
|
63
|
+
# signatures and any opt-in stdlib libraries on top of RBS core.
|
|
64
|
+
#
|
|
65
|
+
# @param root [String, Pathname] project root used to auto-detect
|
|
66
|
+
# the default signature path. Defaults to the current working
|
|
67
|
+
# directory.
|
|
68
|
+
# @param libraries [Array<String, Symbol>] additional stdlib
|
|
69
|
+
# libraries to load on top of {DEFAULT_LIBRARIES}. The
|
|
70
|
+
# final list is the union of the two, de-duplicated while
|
|
71
|
+
# preserving order. Pass an empty array (the default) to
|
|
72
|
+
# load only the defaults.
|
|
73
|
+
# @param signature_paths [Array<String, Pathname>, nil] explicit
|
|
74
|
+
# list of `sig/`-style directories. When `nil` (the default),
|
|
75
|
+
# the canonical project layout `<root>/sig` is used if it
|
|
76
|
+
# exists, otherwise no signature path is loaded.
|
|
77
|
+
# @return [Rigor::Environment]
|
|
78
|
+
def for_project(root: Dir.pwd, libraries: [], signature_paths: nil)
|
|
79
|
+
resolved_paths = signature_paths || default_signature_paths(root)
|
|
80
|
+
merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s)).uniq
|
|
81
|
+
loader = RbsLoader.new(libraries: merged_libraries, signature_paths: resolved_paths)
|
|
82
|
+
new(rbs_loader: loader)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def default_signature_paths(root)
|
|
88
|
+
sig = Pathname(root) / DEFAULT_PROJECT_SIG_DIR
|
|
89
|
+
sig.directory? ? [sig] : []
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Resolves a constant name to a Rigor::Type::Nominal (the *instance*
|
|
94
|
+
# type carrier). Consults the static class registry first (cheap,
|
|
95
|
+
# hardcoded), then falls back to the RBS loader. Returns nil when
|
|
96
|
+
# the name is unknown to both.
|
|
97
|
+
#
|
|
98
|
+
# NOTE: This is the construction helper for "an instance of class
|
|
99
|
+
# `Foo`". For "the class object `Foo` itself" (the value of the
|
|
100
|
+
# constant), use {#singleton_for_name} instead.
|
|
101
|
+
def nominal_for_name(name)
|
|
102
|
+
registered = class_registry.nominal_for_name(name)
|
|
103
|
+
return registered if registered
|
|
104
|
+
|
|
105
|
+
class_known_in_rbs?(name) ? Type::Combinator.nominal_of(name.to_s) : nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Resolves a constant name to a Rigor::Type::Singleton (the *class
|
|
109
|
+
# object* carrier). The expression `Foo` evaluates to the class
|
|
110
|
+
# object, whose RBS type is `singleton(Foo)` -- this method is the
|
|
111
|
+
# corresponding Rigor construction helper.
|
|
112
|
+
#
|
|
113
|
+
# The lookup uses the same registry/RBS chain as {#nominal_for_name}
|
|
114
|
+
# so a class is either known to both queries or to neither.
|
|
115
|
+
def singleton_for_name(name)
|
|
116
|
+
return nil unless class_known?(name)
|
|
117
|
+
|
|
118
|
+
Type::Combinator.singleton_of(name.to_s)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Slice A constant-value lookup. Returns the translated
|
|
122
|
+
# `Rigor::Type` for an RBS-declared **non-class** constant
|
|
123
|
+
# (`Rigor::Analysis::FactStore::BUCKETS: Array[Symbol]`,
|
|
124
|
+
# `Rigor::Configuration::DEFAULT_PATH: String`, ...) or `nil`
|
|
125
|
+
# when no RBS constant declaration covers `name`. This is the
|
|
126
|
+
# value-bearing counterpart of {#singleton_for_name}, which
|
|
127
|
+
# only resolves names that name a class or module. Callers
|
|
128
|
+
# that need to type a `Prism::ConstantReadNode`/
|
|
129
|
+
# `Prism::ConstantPathNode` MUST consult {#singleton_for_name}
|
|
130
|
+
# first and fall through to this query when the constant is
|
|
131
|
+
# not a class.
|
|
132
|
+
def constant_for_name(name)
|
|
133
|
+
return nil if rbs_loader.nil?
|
|
134
|
+
|
|
135
|
+
rbs_loader.constant_type(name.to_s)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Returns true when the constant name is known to either the static
|
|
139
|
+
# registry or the RBS loader. Useful for callers that only need a
|
|
140
|
+
# presence check without materialising a type carrier.
|
|
141
|
+
def class_known?(name)
|
|
142
|
+
return true if class_registry.nominal_for_name(name)
|
|
143
|
+
|
|
144
|
+
class_known_in_rbs?(name)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Compares two class/module names using analyzer-owned class data.
|
|
148
|
+
# Returns `:equal`, `:subclass`, `:superclass`, `:disjoint`, or
|
|
149
|
+
# `:unknown`. The static registry handles built-ins cheaply; the RBS
|
|
150
|
+
# loader handles project/stdlib classes without relying on host Ruby
|
|
151
|
+
# constants being loaded.
|
|
152
|
+
def class_ordering(lhs, rhs)
|
|
153
|
+
lhs = normalize_class_name(lhs)
|
|
154
|
+
rhs = normalize_class_name(rhs)
|
|
155
|
+
return :equal if lhs == rhs
|
|
156
|
+
|
|
157
|
+
registry_result = class_registry.class_ordering(lhs, rhs)
|
|
158
|
+
return registry_result unless registry_result == :unknown
|
|
159
|
+
|
|
160
|
+
return :unknown unless rbs_loader
|
|
161
|
+
|
|
162
|
+
rbs_loader.class_ordering(lhs, rhs)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
def class_known_in_rbs?(name)
|
|
168
|
+
return false unless rbs_loader
|
|
169
|
+
|
|
170
|
+
rbs_loader.class_known?(name)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def normalize_class_name(name)
|
|
174
|
+
name.to_s.delete_prefix("::")
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|