Class: Rigor::Environment::RbsLoader
- Inherits:
-
Object
- Object
- Rigor::Environment::RbsLoader
- Defined in:
- lib/rigor/environment/rbs_loader.rb
Overview
Loads RBS class declarations and method definitions from disk and exposes them to the inference engine in a small, stable surface.
Slice 4 phase 1 only enabled the RBS core signatures shipped with the ‘rbs` gem (`Object`, `Integer`, `String`, `Array`, …). Phase 2a adds opt-in stdlib library loading (`pathname`, `json`, `tempfile`, …) and arbitrary-directory signature loading (typically the project’s local ‘sig/` tree). Both are off by default on `RbsLoader.default` so the core-only fast path stays cheap; project-aware loading is opted into through for_project or by constructing a custom loader.
The default instance is shared across the process: building the core RBS environment costs hundreds of milliseconds and the data is read-only. The shared instance is frozen, but holds a mutable state hash for lazy memoization of the heavy ‘RBS::Environment` and `RBS::DefinitionBuilder` – the user-visible API stays purely functional.
See docs/internal-spec/inference-engine.md for the binding contract. rubocop:disable Metrics/ClassLength
Instance Attribute Summary collapse
-
#cache_store ⇒ Object
readonly
Returns the value of attribute cache_store.
-
#libraries ⇒ Object
readonly
Returns the value of attribute libraries.
-
#signature_paths ⇒ Object
readonly
Returns the value of attribute signature_paths.
Class Method Summary collapse
-
.build_env_for(libraries:, signature_paths:) ⇒ Object
Builds an ‘RBS::Environment` from explicit `libraries` and `signature_paths`.
- .default ⇒ Object
-
.reset_default! ⇒ Object
Used by tests to discard the cached default loader; production code MUST NOT call this.
- .vendored_gem_sig_paths ⇒ Object
Instance Method Summary collapse
-
#class_decl_paths ⇒ Object
Returns a frozen ‘Hash<String, String>` mapping each loaded class / module name (top-level prefixed) to the file path of its FIRST declaration’s RBS source.
-
#class_known?(name) ⇒ Boolean
Returns true when an RBS class or module declaration with the given name is loaded.
- #class_ordering(lhs, rhs) ⇒ Object
-
#class_type_param_names(class_name) ⇒ Object
Slice 4 phase 2d.
-
#constant_names ⇒ Array<String>
Every RBS-declared constant name (top-level prefixed, e.g., ‘“::Math::PI”`) currently loaded into the environment.
-
#constant_type(name) ⇒ Object
Slice A constant-value lookup.
-
#each_constant_decl ⇒ Object
Yields ‘(name, entry)` for every RBS constant declaration currently loaded into the environment.
-
#each_known_class_name ⇒ Object
Yields every known class / module / alias name (top-level prefixed) currently loaded into the environment.
-
#initialize(libraries: [], signature_paths: [], cache_store: nil) ⇒ RbsLoader
constructor
A new instance of RbsLoader.
-
#instance_definition(class_name) ⇒ RBS::Definition?
When ‘cache_store` is set, the loader fetches the per-class definition through Cache::RbsInstanceDefinitions.fetch so subsequent runs (and other loaders sharing the same Store) skip the `RBS::DefinitionBuilder.build_instance` step.
- #instance_method(class_name:, method_name:) ⇒ RBS::Definition::Method?
-
#prewarm ⇒ Object
ADR-15 Phase 4b.x — eagerly drives every cached producer so a subsequent worker Ractor can serve all of its RBS queries from the Marshal blob on disk without ever calling ‘RBS::EnvironmentLoader.new`.
-
#reflection ⇒ Object
ADR-15 Phase 2b — return the loader’s read-only query surface as a frozen, ‘Ractor.shareable?` Reflection value object.
-
#singleton_definition(class_name) ⇒ RBS::Definition?
When ‘cache_store` is set, the loader fetches the per-class singleton definition through Cache::RbsSingletonDefinitions.fetch; the same caching discipline as #instance_definition.
-
#singleton_method(class_name:, method_name:) ⇒ RBS::Definition::Method?
The class method on ‘class_name`.
-
#uncached_instance_definition(class_name) ⇒ Object
Public uncached accessor used by the cache producer (Cache::RbsInstanceDefinitions).
-
#uncached_singleton_definition(class_name) ⇒ Object
Public uncached accessor used by the cache producer.
Constructor Details
#initialize(libraries: [], signature_paths: [], cache_store: nil) ⇒ RbsLoader
Returns a new instance of RbsLoader.
117 118 119 120 121 122 123 124 125 126 |
# File 'lib/rigor/environment/rbs_loader.rb', line 117 def initialize(libraries: [], signature_paths: [], cache_store: nil) @libraries = libraries.map(&:to_s).freeze @signature_paths = signature_paths.map { |p| Pathname(p) }.freeze @cache_store = cache_store @state = { env: nil, builder: nil } @instance_definition_cache = {} @singleton_definition_cache = {} @class_known_cache = {} @hierarchy = RbsHierarchy.new(self) end |
Instance Attribute Details
#cache_store ⇒ Object (readonly)
Returns the value of attribute cache_store.
98 99 100 |
# File 'lib/rigor/environment/rbs_loader.rb', line 98 def cache_store @cache_store end |
#libraries ⇒ Object (readonly)
Returns the value of attribute libraries.
98 99 100 |
# File 'lib/rigor/environment/rbs_loader.rb', line 98 def libraries @libraries end |
#signature_paths ⇒ Object (readonly)
Returns the value of attribute signature_paths.
98 99 100 |
# File 'lib/rigor/environment/rbs_loader.rb', line 98 def signature_paths @signature_paths end |
Class Method Details
.build_env_for(libraries:, signature_paths:) ⇒ Object
Builds an ‘RBS::Environment` from explicit `libraries` and `signature_paths`. Stateless surface so the v0.0.9 Cache::RbsEnvironment producer can build an env on cache miss without holding a loader instance, and the instance-side #build_env delegates here so the implementation stays single-rooted.
Vendored gem stubs (‘data/vendored_gem_sigs/<gem>/`) are loaded on top of `signature_paths` so the per-gem RBS bundled with Rigor itself is in scope for every analysis run. The gem stubs are intentionally read-only and appended LAST so user-supplied `signature_paths` win on name conflicts.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/rigor/environment/rbs_loader.rb', line 59 def build_env_for(libraries:, signature_paths:) rbs_loader = RBS::EnvironmentLoader.new libraries.each do |library| next unless rbs_loader.has_library?(library: library, version: nil) rbs_loader.add(library: library, version: nil) end signature_paths.each do |path| path = Pathname(path) unless path.is_a?(Pathname) rbs_loader.add(path: path) if path.directory? end vendored_gem_sig_paths.each do |path| rbs_loader.add(path: path) if path.directory? end RBS::Environment.from_loader(rbs_loader).resolve_type_names end |
.default ⇒ Object
34 35 36 |
# File 'lib/rigor/environment/rbs_loader.rb', line 34 def default @default ||= new.freeze end |
.reset_default! ⇒ Object
Used by tests to discard the cached default loader; production code MUST NOT call this. The shared loader holds a several-MB RBS::Environment, so dropping it during a normal run wastes the cost of rebuilding it.
42 43 44 |
# File 'lib/rigor/environment/rbs_loader.rb', line 42 def reset_default! @default = nil end |
.vendored_gem_sig_paths ⇒ Object
89 90 91 92 93 94 95 |
# File 'lib/rigor/environment/rbs_loader.rb', line 89 def vendored_gem_sig_paths return [] unless File.directory?(VENDORED_GEM_SIGS_ROOT) Dir.children(VENDORED_GEM_SIGS_ROOT).map do |gem_dir| Pathname(File.join(VENDORED_GEM_SIGS_ROOT, gem_dir)) end end |
Instance Method Details
#class_decl_paths ⇒ Object
Returns a frozen ‘Hash<String, String>` mapping each loaded class / module name (top-level prefixed) to the file path of its FIRST declaration’s RBS source. Used by Analysis::RunStats to attribute the type universe between “project sig/” (paths under the configured ‘signature_paths`) and “bundled” (everything else — RBS core, stdlib libraries, gem-bundled RBS). Each value is a frozen `String` so the whole result is `Ractor.shareable?` — the Phase 4b worker pool ships a snapshot back to the coordinator on the first `:prepare` message.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/rigor/environment/rbs_loader.rb', line 178 def class_decl_paths return {}.freeze if env.nil? result = {} env.class_decls.each do |rbs_name, entry| decl = entry.primary_decl next if decl.nil? location = decl.location next if location.nil? buffer = location.buffer name = buffer.respond_to?(:name) ? buffer.name : nil next if name.nil? result[rbs_name.to_s.dup.freeze] = name.to_s.dup.freeze end result.freeze rescue ::RBS::BaseError {}.freeze end |
#class_known?(name) ⇒ Boolean
Returns true when an RBS class or module declaration with the given name is loaded. Accepts unprefixed or top-level-prefixed names (“Integer” or “::Integer”). Memoized per-name (positive and negative results both cache).
When ‘cache_store` is set, the loader fetches the entire set of known class / module / alias names once (per process) through Cache::RbsKnownClassNames.fetch and answers `class_known?` from the in-memory Set. Cold runs pay a single env walk and persist the result; warm runs (and a separate loader sharing the same Store) skip the env walk entirely.
139 140 141 142 143 144 145 146 147 148 |
# File 'lib/rigor/environment/rbs_loader.rb', line 139 def class_known?(name) key = name.to_s return @class_known_cache[key] if @class_known_cache.key?(key) @class_known_cache[key] = if cache_store cached_class_known(name) else compute_class_known(name) end end |
#class_ordering(lhs, rhs) ⇒ Object
307 308 309 |
# File 'lib/rigor/environment/rbs_loader.rb', line 307 def class_ordering(lhs, rhs) @hierarchy.class_ordering(lhs, rhs) end |
#class_type_param_names(class_name) ⇒ Object
Slice 4 phase 2d. Returns the class’s declared type-parameter names as Symbols (e.g., ‘[:Elem]` for `Array`, `[:K, :V]` for `Hash`). Used by the dispatcher to build the substitution map from receiver `type_args` into the method’s return type. The instance definition is the canonical source because singleton methods (e.g., ‘Array.new`) parameterize over the same `Elem` as instance methods.
Returns an empty array for non-generic classes and for unknown names (the loader stays fail-soft). NOTE: in the ‘rbs` gem, `RBS::Definition#type_params` returns `Array<Symbol>` directly, not the AST `TypeParam` object (those live on the AST level).
When ‘cache_store` is set, the loader fetches the entire type-parameter-name table once (per process) through Cache::RbsClassTypeParamNames.fetch and answers point lookups from it. Cold runs build the table once and persist it; warm runs (and a separate loader sharing the same Store) skip the env walk entirely.
295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/rigor/environment/rbs_loader.rb', line 295 def class_type_param_names(class_name) if cache_store key = class_name.to_s.delete_prefix("::") return type_param_names_table.fetch(key, []).dup end definition = instance_definition(class_name) return [] unless definition definition.type_params.dup end |
#constant_names ⇒ Array<String>
Returns every RBS-declared constant name (top-level prefixed, e.g., ‘“::Math::PI”`) currently loaded into the environment. Used by the cache producer that materialises the constant-type table; ordinary callers should keep using #constant_type for point lookups.
316 317 318 319 320 321 322 |
# File 'lib/rigor/environment/rbs_loader.rb', line 316 def constant_names return [] if env.nil? env.constant_decls.keys.map(&:to_s) rescue ::RBS::BaseError [] end |
#constant_type(name) ⇒ Object
Slice A constant-value lookup. Returns the translated ‘Rigor::Type` for a non-class constant declaration (`BUCKETS: Array`, `DEFAULT_PATH: String`, …) or `nil` when no constant entry exists for `name` in the loaded RBS environment. Callers MUST treat the return value as authoritative when present and as “unknown” when nil; the loader does NOT consult the class declarations here — class objects are still resolved through #class_known? and `Environment#singleton_for_name`.
When ‘cache_store` is set, the loader fetches the entire translated constant table once (per process) through Cache::RbsConstantTable.fetch and answers point lookups from it. Cold runs pay the translation cost up-front and write the result to disk; warm runs skip the translation entirely and pay only a `Marshal.load` of the table.
356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/rigor/environment/rbs_loader.rb', line 356 def constant_type(name) rbs_name = parse_type_name(name) return nil unless rbs_name if cache_store constant_type_table[rbs_name.to_s] else translate_constant_decl(rbs_name) end rescue ::RBS::BaseError nil end |
#each_constant_decl ⇒ Object
Yields ‘(name, entry)` for every RBS constant declaration currently loaded into the environment. The cache producer uses this to materialise the constant-type table without going back through #constant_type (which would recurse back into the cache when `cache_store` is set).
329 330 331 332 333 334 335 336 337 338 |
# File 'lib/rigor/environment/rbs_loader.rb', line 329 def each_constant_decl return enum_for(:each_constant_decl) unless block_given? return if env.nil? env.constant_decls.each do |rbs_name, entry| yield rbs_name.to_s, entry end rescue ::RBS::BaseError # fail-soft: a broken RBS environment yields no entries. end |
#each_known_class_name ⇒ Object
Yields every known class / module / alias name (top-level prefixed) currently loaded into the environment. The cache producer that materialises the known-name set uses this so it never recurses back through #class_known?.
154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/rigor/environment/rbs_loader.rb', line 154 def each_known_class_name return enum_for(:each_known_class_name) unless block_given? return if env.nil? env.class_decls.each_key { |rbs_name| yield rbs_name.to_s } env.class_alias_decls.each_key { |rbs_name| yield rbs_name.to_s } rescue ::RBS::BaseError # fail-soft: a broken RBS environment yields no names. # Analyzer-internal errors (NameError, NoMethodError, # LoadError) are NOT swallowed — those are bugs and # must surface so they don't hide silently the way the # v0.0.9 cache `Cache::Descriptor` regression did. end |
#instance_definition(class_name) ⇒ RBS::Definition?
When ‘cache_store` is set, the loader fetches the per-class definition through Cache::RbsInstanceDefinitions.fetch so subsequent runs (and other loaders sharing the same Store) skip the `RBS::DefinitionBuilder.build_instance` step. In-memory `@instance_definition_cache` keeps the per-process short-circuit on top.
211 212 213 214 215 216 217 218 219 220 |
# File 'lib/rigor/environment/rbs_loader.rb', line 211 def instance_definition(class_name) key = class_name.to_s return @instance_definition_cache[key] if @instance_definition_cache.key?(key) @instance_definition_cache[key] = if cache_store cached_instance_definition(class_name) else build_instance_definition(class_name) end end |
#instance_method(class_name:, method_name:) ⇒ RBS::Definition::Method?
231 232 233 234 235 236 |
# File 'lib/rigor/environment/rbs_loader.rb', line 231 def instance_method(class_name:, method_name:) definition = instance_definition(class_name) return nil unless definition definition.methods[method_name.to_sym] end |
#prewarm ⇒ Object
ADR-15 Phase 4b.x — eagerly drives every cached producer so a subsequent worker Ractor can serve all of its RBS queries from the Marshal blob on disk without ever calling ‘RBS::EnvironmentLoader.new`. The loader path that calls `EnvironmentLoader.new` transitively reads a chain of non-`Ractor.shareable?` module constants (`RBS::EnvironmentLoader::DEFAULT_CORE_ROOT`, `RBS::Repository::DEFAULT_STDLIB_ROOT`, `Gem::Requirement::DefaultRequirement`, …) and trips `Ractor::IsolationError`. Pre-warming the cache on the main Ractor and letting workers consult ONLY the Marshal-loaded blob sidesteps the whole chain.
No-op when ‘cache_store` is nil — without a Store the worker has no choice but to build env via the loader, so the caller MUST ensure pool mode runs with caching enabled. Returns `self` so the call chains cleanly from the `Runner` pre-spawn hook.
388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'lib/rigor/environment/rbs_loader.rb', line 388 def prewarm return self if cache_store.nil? env known_class_names_set constant_type_table type_param_names_table ancestor_names_table instance_definitions_table singleton_definitions_table self end |
#reflection ⇒ Object
ADR-15 Phase 2b — return the loader’s read-only query surface as a frozen, ‘Ractor.shareable?` Rigor::Environment::Reflection value object. Built lazily on first access; the loader memoises so repeated calls return the same instance.
The Reflection consumes the loader’s already-warmed cache producers (or, when no ‘cache_store` is set, eagerly walks the env). Once constructed, the Reflection carries the derived tables independently and never re-consults the loader — making it safe to share across Ractors while the loader stays per- process / per-Ractor for write-path operations.
414 415 416 417 418 419 420 421 422 423 424 425 426 |
# File 'lib/rigor/environment/rbs_loader.rb', line 414 def reflection @state[:reflection] ||= begin require_relative "reflection" Environment::Reflection.new( known_class_names: known_class_names_set, instance_definitions: instance_definitions_table, singleton_definitions: singleton_definitions_table, type_param_names: type_param_names_table, constant_types: constant_type_table, ancestor_names: ancestor_names_table ) end end |
#singleton_definition(class_name) ⇒ RBS::Definition?
When ‘cache_store` is set, the loader fetches the per-class singleton definition through Cache::RbsSingletonDefinitions.fetch; the same caching discipline as #instance_definition.
248 249 250 251 252 253 254 255 256 257 |
# File 'lib/rigor/environment/rbs_loader.rb', line 248 def singleton_definition(class_name) key = class_name.to_s return @singleton_definition_cache[key] if @singleton_definition_cache.key?(key) @singleton_definition_cache[key] = if cache_store cached_singleton_definition(class_name) else build_singleton_definition(class_name) end end |
#singleton_method(class_name:, method_name:) ⇒ RBS::Definition::Method?
Returns the class method on ‘class_name`. For example, `singleton_method(class_name: “Integer”, method_name: :sqrt)` returns the definition for `Integer.sqrt`, while `singleton_method(class_name: “Foo”, method_name: :new)` returns Class#new for any class type.
269 270 271 272 273 274 |
# File 'lib/rigor/environment/rbs_loader.rb', line 269 def singleton_method(class_name:, method_name:) definition = singleton_definition(class_name) return nil unless definition definition.methods[method_name.to_sym] end |
#uncached_instance_definition(class_name) ⇒ Object
Public uncached accessor used by the cache producer (Cache::RbsInstanceDefinitions). Avoids the ‘private_method_called` round-trip a `loader.send(…)` callsite would require.
226 227 228 |
# File 'lib/rigor/environment/rbs_loader.rb', line 226 def uncached_instance_definition(class_name) build_instance_definition(class_name) end |
#uncached_singleton_definition(class_name) ⇒ Object
Public uncached accessor used by the cache producer.
260 261 262 |
# File 'lib/rigor/environment/rbs_loader.rb', line 260 def uncached_singleton_definition(class_name) build_singleton_definition(class_name) end |